在咱們平常的Java Web開發中,無不都是使用數據庫來進行數據的存儲,因爲通常的系統任務中一般不會存在高併發的狀況,因此這樣看起來並無什麼問題,但是一旦涉及大數據量的需求,好比一些商品搶購的情景,或者是主頁訪問量瞬間較大的時候,單一使用數據庫來保存數據的系統會由於面向磁盤,磁盤讀/寫速度比較慢的問題而存在嚴重的性能弊端,一瞬間成千上萬的請求到來,須要系統在極短的時間內完成成千上萬次的讀/寫操做,這個時候每每不是數據庫可以承受的,極其容易形成數據庫系統癱瘓,最終致使服務宕機的嚴重生產問題。html
爲了克服上述的問題,Java Web項目一般會引入NoSQL技術,這是一種基於內存的數據庫,而且提供必定的持久化功能。git
Redis和MongoDB是當前使用最普遍的NoSQL,而就Redis技術而言,它的性能十分優越,能夠支持每秒十幾萬此的讀/寫操做,其性能遠超數據庫,而且還支持集羣、分佈式、主從同步等配置,原則上能夠無限擴展,讓更多的數據存儲在內存中,更讓人欣慰的是它還支持必定的事務能力,這保證了高併發的場景下數據的安全和一致性。github
Redis 在 Java Web 主要有兩個應用場景:redis
在平常對數據庫的訪問中,讀操做的次數遠超寫操做,比例大概在 1:9 到 3:7,因此須要讀的可能性是比寫的可能大得多的。當咱們使用SQL語句去數據庫進行讀寫操做時,數據庫就會去磁盤把對應的數據索引取回來,這是一個相對較慢的過程。spring
若是咱們把數據放在 Redis 中,也就是直接放在內存之中,讓服務端直接去讀取內存中的數據,那麼這樣速度明顯就會快上很多,而且會極大減少數據庫的壓力,可是使用內存進行數據存儲開銷也是比較大的,限於成本的緣由,通常咱們只是使用 Redis 存儲一些經常使用和主要的數據,好比用戶登陸的信息等。數據庫
通常而言在使用 Redis 進行存儲的時候,咱們須要從如下幾個方面來考慮:windows
在考慮了這些問題以後,若是以爲有必要使用緩存,那麼就使用它!使用 Redis 做爲緩存的讀取邏輯以下圖所示:緩存
從上圖咱們能夠知道如下兩點:安全
從上面的分析能夠知道,讀操做的可能性是遠大於寫操做的,因此使用 Redis 來處理平常中須要常常讀取的數據,速度提高是顯而易見的,同時也下降了對數據庫的依賴,使得數據庫的壓力大大減小。springboot
分析了讀操做的邏輯,下面咱們來看看寫操做的流程:
從流程能夠看出,更新或者寫入的操做,須要多個 Redis 的操做,若是業務數據寫次數遠大於讀次數那麼就沒有必要使用 Redis。
關於使用內存存儲數據,我知道谷歌好像就是把全部互聯網的數據都存儲在內存條的,因此纔會有如此高質量、高效的搜索,但它畢竟是谷歌...
在現在的互聯網中,愈來愈多的存在高併發的狀況,好比天貓雙十一、搶紅包、搶演唱會門票等,這些場合都是在某一個瞬間或者是某一個短暫的時刻有成千上萬的請求到達服務器,若是單純的使用數據庫來進行處理,就算不崩,也會很慢的,輕則形成用戶體驗極差用戶量流失,重則數據庫癱瘓,服務宕機,而這樣的場合都是不容許的!
因此咱們須要使用 Redis 來應對這樣的高併發需求的場合,咱們先來看看一次請求操做的流程圖:
咱們來進一步闡述這個過程:
訪問地址:https://github.com/ServiceStack/redis-windows/tree/master/downloads
把 Redis 下載下來後找到一個合適的地方解壓,就能獲得以下圖所示的目錄(這裏空格被替換成了%20...):
爲了方便啓動,咱們在該目錄下新建一個 startup.cmd 的文件,而後將如下內容寫入文件:
redis-server redis.windows.conf
這個命令其實就是在調用 redis-server.exe 命令來讀取 redis.window.conf 的內容,咱們雙擊剛纔建立好的 startup.cmd 文件,就能成功的看到 Redis 啓動:
上圖的提示信息告訴了咱們:① Redis 當前的版本爲 3.0.503;② Redis 運行在 6379 端口;③ Redis 進程的 PID 爲 14748;④ 64 位。
咱們能夠打開同一個文件夾下的 redis-cli.exe 文件,這是 Redis 自帶的一個客戶端工具,它能夠用來鏈接到咱們當前的 Redis 服務器,咱們作如下測試:
如此,咱們便在 Windows 的環境下安裝好了 Redis。
想要在 Java 中使用 Redis 緩存,須要添加相關的Jar包依賴,打開Maven倉庫的網站:https://mvnrepository.com/ ,搜索Jedis:
把它導入工程中去就能夠啦,下面咱們來對Redis的寫入性能作一下測試:
@Test public void redisTester() { Jedis jedis = new Jedis("localhost", 6379, 100000); int i = 0; try { long start = System.currentTimeMillis();// 開始毫秒數 while (true) { long end = System.currentTimeMillis(); if (end - start >= 1000) {// 當大於等於1000毫秒(至關於1秒)時,結束操做 break; } i++; jedis.set("test" + i, i + ""); } } finally {// 關閉鏈接 jedis.close(); } // 打印1秒內對Redis的操做次數 System.out.println("redis每秒操做:" + i + "次"); } -----------測試結果----------- redis每秒操做:10734次
聽說 Redis 的性能能達到十萬級別,我不敢相信個人臺式機電腦只有十分之一不到的性能,雖說這裏不是流水線的操做,會形成必定的影響,但我仍是不信邪,我查到了官方的性能測試方法:
首先在Redis根目錄下召喚Cmd:具體方法是按住【Shift】點擊右鍵
而後輸入命令:【redis-benchmark -n 100000 -q】:來同時執行10萬個請求測試性能
好吧,我同時在個人筆記本上測試了一下,結果更加慘淡...low啊low...
跟數據庫鏈接池相同,Java Redis也一樣提供了類redis.clients.jedis.JedisPool
來管理咱們的Reids鏈接池對象,而且咱們可使用redis.clients.jedis.JedisPoolConfig
來對鏈接池進行配置,代碼以下:
JedisPoolConfig poolConfig = new JedisPoolConfig(); // 最大空閒數 poolConfig.setMaxIdle(50); // 最大鏈接數 poolConfig.setMaxTotal(100); // 最大等待毫秒數 poolConfig.setMaxWaitMillis(20000); // 使用配置建立鏈接池 JedisPool pool = new JedisPool(poolConfig, "localhost"); // 從鏈接池中獲取單個鏈接 Jedis jedis = pool.getResource(); // 若是須要密碼 //jedis.auth("password");
Redis 只能支持六種數據類型(string/hash/list/set/zset/hyperloglog)的操做,但在 Java 中咱們卻一般以類對象爲主,因此在須要 Redis 存儲的五中數據類型與 Java 對象之間進行轉換,若是本身編寫一些工具類,好比一個角色對象的轉換,仍是比較容易的,可是涉及到許多對象的時候,這其中不管工做量仍是工做難度都是很大的,因此整體來講,就操做對象而言,使用 Redis 仍是挺難的,好在 Spring 對這些進行了封裝和支持。
上面說到了 Redis 沒法操做對象的問題,沒法在那些基礎類型和 Java 對象之間方便的轉換,可是在 Spring 中,這些問題均可以經過使用RedisTemplate獲得解決!
想要達到這樣的效果,除了 Jedis 包之外還須要在 Spring 引入 spring-data-redis 包:https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis
這裏把2.0.7最新版本標紅的意思是:別老想着使用最新的Jar包,特別是涉及到框架的一些東西,筆者用實際的操做體驗告訴大家,引入該版本的包是會致使Jar包衝突的(也就是莫名其妙的錯誤),我乖乖換回了1.7.2的版本,代碼就通了...咱們來看看怎麼作吧:
(1)第一步:使用Spring配置JedisPoolConfig對象
大部分的狀況下,咱們仍是會用到鏈接池的,因而先用 Spring 配置一個 JedisPoolConfig 對象:
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!--最大空閒數--> <property name="maxIdle" value="50"/> <!--最大鏈接數--> <property name="maxTotal" value="100"/> <!--最大等待時間--> <property name="maxWaitMillis" value="20000"/> </bean>
(2)第二步:爲鏈接池配置工廠模型
好了,咱們如今配置好了鏈接池的相關屬性,那麼具體使用哪一種工廠實現呢?在Spring Data Redis中有四種可供咱們選擇的工廠模型,它們分別是:
咱們這裏就簡單配置成JedisConnectionFactory:
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <!--Redis服務地址--> <property name="hostName" value="localhost"/> <!--端口號--> <property name="port" value="6379"/> <!--若是有密碼則須要配置密碼--> <!--<property name="password" value="password"/>--> <!--鏈接池配置--> <property name="poolConfig" ref="poolConfig"/> </bean>
(3)第三步:配置RedisTemplate
普通的鏈接根本沒有辦法直接將對象直接存入 Redis 內存中,咱們須要替代的方案:將對象序列化(能夠簡單的理解爲繼承Serializable接口)。咱們能夠把對象序列化以後存入Redis緩存中,而後在取出的時候又經過轉換器,將序列化以後的對象反序列化回對象,這樣就完成了咱們的要求:
RedisTemplate能夠幫助咱們完成這份工做,它會找到對應的序列化器去轉換Redis的鍵值:
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="connectionFactory"/>
筆者從《JavaEE互聯網輕量級框架整合開發》中瞭解到,這一步須要配置單獨的序列化器去支撐這一步的工做,可是本身在測試當中,發現只要咱們的POJO類實現了Serializable接口,就不會出現問題...因此我直接省略掉了配置序列化器這一步...
(4)第四步:編寫測試
首先編寫好支持咱們測試的POJO類:
/** * @author: @我沒有三顆心臟 * @create: 2018-05-30-下午 22:31 */ public class Student implements Serializable{ private String name; private int age; /** * 給該類一個服務類用於測試 */ public void service() { System.out.println("學生名字爲:" + name); System.out.println("學生年齡爲:" + age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
而後編寫測試類:
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); RedisTemplate redisTemplate = context.getBean(RedisTemplate.class); Student student = new Student(); student.setName("我沒有三顆心臟"); student.setAge(21); redisTemplate.opsForValue().set("student_1", student); Student student1 = (Student) redisTemplate.opsForValue().get("student_1"); student1.service(); }
運行能夠成功看到結果:
(1)在SpringBoot中添加Redis依賴:
<!-- Radis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(2)添加配置文件:
在SpringBoot中使用.properties
或者.yml
均可以,這裏給出.properties
的例子,由於本身的.yml
文件看上去感受亂糟糟的:
# REDIS (RedisProperties) # Redis數據庫索引(默認爲0) spring.redis.database=0 # Redis服務器地址 spring.redis.host=localhost # Redis服務器鏈接端口 spring.redis.port=6379 # Redis服務器鏈接密碼(默認爲空) spring.redis.password= # 鏈接池最大鏈接數(使用負值表示沒有限制) spring.redis.pool.max-active=8 # 鏈接池最大阻塞等待時間(使用負值表示沒有限制) spring.redis.pool.max-wait=-1 # 鏈接池中的最大空閒鏈接 spring.redis.pool.max-idle=8 # 鏈接池中的最小空閒鏈接 spring.redis.pool.min-idle=0 # 鏈接超時時間(毫秒) spring.redis.timeout=0
(3)測試訪問:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest() public class ApplicationTests { @Autowired private StringRedisTemplate stringRedisTemplate; @Test public void test() throws Exception { // 保存字符串 stringRedisTemplate.opsForValue().set("aaa", "111"); Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa")); } }
經過上面這段極爲簡單的測試案例演示瞭如何經過自動配置的StringRedisTemplate對象進行Redis的讀寫操做,該對象從命名中就可注意到支持的是String類型。本來是RedisTemplate<K, V>接口,StringRedisTemplate就至關於RedisTemplate<String, String>的實現。
運行測試,若是一切成功則不會報錯,若是咱們沒有拿到或者拿到的數不是咱們想要的 「111」 ,那麼則會報錯,這是使用Assert的好處(下面是我改爲112以後運行報錯的結果):
(4)存儲對象:
這一步跟上面使用Spring同樣,只須要將POJO類實現Serializable接口就能夠了,我這裏就貼一下測試代碼:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest() public class ApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test public void test() throws Exception { User user = new User(); user.setName("我沒有三顆心臟"); user.setAge(21); redisTemplate.opsForValue().set("user_1", user); User user1 = (User) redisTemplate.opsForValue().get("user_1"); System.out.println(user1.getName()); } }
仍然沒有任何問題:
參考文章:
1.http://www.javashuo.com/article/p-azjgnmws-go.html
2.http://blog.didispace.com/springbootredis/
直接黏上兩段簡單的示例代碼:
// list數據類型適合於消息隊列的場景:好比12306併發量過高,而同一時間段內只能處理指定數量的數據!必須知足先進先出的原則,其他數據處於等待 @Test public void listPushResitTest() { // leftPush依次由右邊添加 stringRedisTemplate.opsForList().rightPush("myList", "1"); stringRedisTemplate.opsForList().rightPush("myList", "2"); stringRedisTemplate.opsForList().rightPush("myList", "A"); stringRedisTemplate.opsForList().rightPush("myList", "B"); // leftPush依次由左邊添加 stringRedisTemplate.opsForList().leftPush("myList", "0"); } @Test public void listGetListResitTest() { // 查詢類別全部元素 List<String> listAll = stringRedisTemplate.opsForList().range("myList", 0, -1); logger.info("list all {}", listAll); // 查詢前3個元素 List<String> list = stringRedisTemplate.opsForList().range("myList", 0, 3); logger.info("list limit {}", list); } @Test public void listRemoveOneResitTest() { // 刪除先進入的B元素 stringRedisTemplate.opsForList().remove("myList", 1, "B"); } @Test public void listRemoveAllResitTest() { // 刪除全部A元素 stringRedisTemplate.opsForList().remove("myList", 0, "A"); }
@Test public void hashPutResitTest() { // map的key值相同,後添加的覆蓋原有的 stringRedisTemplate.opsForHash().put("banks:12600000", "a", "b"); } @Test public void hashGetEntiresResitTest() { // 獲取map對象 Map<Object, Object> map = stringRedisTemplate.opsForHash().entries("banks:12600000"); logger.info("objects:{}", map); } @Test public void hashGeDeleteResitTest() { // 根據map的key刪除這個元素 stringRedisTemplate.opsForHash().delete("banks:12600000", "c"); } @Test public void hashGetKeysResitTest() { // 得到map的key集合 Set<Object> objects = stringRedisTemplate.opsForHash().keys("banks:12600000"); logger.info("objects:{}", objects); } @Test public void hashGetValueListResitTest() { // 得到map的value列表 List<Object> objects = stringRedisTemplate.opsForHash().values("banks:12600000"); logger.info("objects:{}", objects); } @Test public void hashSize() { // 獲取map對象大小 long size = stringRedisTemplate.opsForHash().size("banks:12600000"); logger.info("size:{}", size); }
在網上看到了關於MySQL的性能測試,讀寫操做大概就每秒1000如下的樣子,並且這還和引擎相關,因此能夠看出Redis確實能在性能方面幫助許多,此博客是轉載保存,之後方便查看,感謝做者:我沒有三顆心臟