Redis
1. 概念: redis是一款高性能的NOSQL系列的非關係型數據庫
1.1.什麼是NOSQL
NoSQL(NoSQL = Not Only SQL),意即“不僅僅是SQL”,是一項全新的數據庫理念,泛指非關係型的數據庫。
隨着互聯網web2.0網站的興起,傳統的關係數據庫在應付web2.0網站,特別是超大規模和高併發的SNS類型的web2.0純動態網站已經顯得力不從心,暴露了很多難以克服的問題,而非關係型的數據庫則由於其本身的特點得到了非常迅速的發展。NoSQL數據庫的產生就是爲了解決大規模數據集合多重數據種類帶來的挑戰,尤其是大數據應用難題。
1.1.1. NOSQL和關係型數據庫比較
優點:
1)成本:nosql數據庫簡單易部署,基本都是開源軟件,不需要像使用oracle那樣花費大量成本購買使用,相比關係型數據庫價格便宜。
2)查詢速度:nosql數據庫將數據存儲於緩存之中,關係型數據庫將數據存儲在硬盤中,自然查詢速度遠不及nosql數據庫。
3)存儲數據的格式:nosql的存儲格式是key,value形式、文檔形式、圖片形式等等,所以可以存儲基礎類型以及對象或者是集合等各種格式,而數據庫則只支持基礎類型。
4)擴展性:關係型數據庫有類似join這樣的多表查詢機制的限制導致擴展很艱難。
缺點:
1)維護的工具和資料有限,因爲nosql是屬於新的技術,不能和關係型數據庫10幾年的技術同日而語。
2)不提供對sql的支持,如果不支持sql這樣的工業標準,將產生一定用戶的學習和使用成本。
1.1.2. 非關係型數據庫的優勢:
1)性能NOSQL是基於鍵值對的,可以想象成表中的主鍵和值的對應關係,而且不需要經過SQL層的解析,所以性能非常高。
2)可擴展性同樣也是因爲基於鍵值對,數據之間沒有耦合性,所以非常容易水平擴展。
1.1.3. 關係型數據庫的優勢:
1)複雜查詢可以用SQL語句方便的在一個表以及多個表之間做非常複雜的數據查詢。
2)事務支持使得對於安全性能很高的數據訪問要求得以實現。對於這兩類數據庫,對方的優勢就是自己的弱勢,反之亦然。
1.1.4. 總結
關係型數據庫與NoSQL數據庫並非對立而是互補的關係,即通常情況下使用關係型數據庫,在適合使用NoSQL的時候使用NoSQL數據庫,
讓NoSQL數據庫對關係型數據庫的不足進行彌補。
一般會將數據存儲在關係型數據庫中,在nosql數據庫中備份存儲關係型數據庫的數據
1.2.主流的NOSQL產品
• 鍵值(Key-Value)存儲數據庫
相關產品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型應用: 內容緩存,主要用於處理大量數據的高訪問負載。
數據模型: 一系列鍵值對
優勢: 快速查詢
劣勢: 存儲的數據缺少結構化
• 列存儲數據庫
相關產品:Cassandra, HBase, Riak
典型應用:分佈式的文件系統
數據模型:以列簇式存儲,將同一列數據存在一起
優勢:查找速度快,可擴展性強,更容易進行分佈式擴展
劣勢:功能相對侷限
• 文檔型數據庫
相關產品:CouchDB、MongoDB
典型應用:Web應用(與Key-Value類似,Value是結構化的)
數據模型: 一系列鍵值對
優勢:數據結構要求不嚴格
劣勢: 查詢性能不高,而且缺乏統一的查詢語法
• 圖形(Graph)數據庫
相關數據庫:Neo4J、InfoGrid、Infinite Graph
典型應用:社交網絡
數據模型:圖結構
優勢:利用圖結構相關算法。
劣勢:需要對整個圖做計算才能得出結果,不容易做分佈式的集羣方案。
1.3 什麼是Redis
Redis是用C語言開發的一個開源的高性能鍵值對(key-value)數據庫,官方提供測試數據,50個併發執行100000個請求,讀的速度是110000次/s,寫的速度是81000次/s ,且Redis通過提供多種鍵值數據類型來適應不同場景下的存儲需求,目前爲止Redis支持的鍵值數據類型如下:
1) 字符串類型 string
2) 哈希類型 hash
3) 列表類型 list
4) 集合類型 set
5) 有序集合類型 sortedset
1.3.1 redis的應用場景
• 緩存(數據查詢、短連接、新聞內容、商品內容等等)
• 聊天室的在線好友列表
• 任務隊列。(秒殺、搶購、12306等等)
• 應用排行榜
• 網站訪問統計
• 數據過期處理(可以精確到毫秒
• 分佈式集羣架構中的session分離
2. 下載安裝
- 官網:https://redis.io
- 中文網:http://www.redis.net.cn/
- 解壓直接可以使用:
- redis.windows.conf:配置文件
- redis-cli.exe:redis的客戶端
- redis-server.exe:redis服務器端
3. 命令操作
- redis的數據結構:
redis存儲的是:key,value格式的數據,其中key都是字符串,value有5種不同的數據結構
value的數據結構:
- 字符串類型 string
- 哈希類型 hash : map格式
- 列表類型 list : LinkedList格式。支持重複元素
- 集合類型 set : 不允許重複元素 HashSet
- 有序集合類型 sortedset:不允許重複元素,且元素有順序
如圖所示:
- 字符串類型 string------------一個鍵最大能存儲512MB。
- 存儲: set key value
127.0.0.1:6379> set username zhangsan
OK- 獲取: get key
127.0.0.1:6379> get username
“zhangsan”- 刪除: del key
127.0.0.1:6379> del age
(integer) 1
- 哈希類型 hash---------------適合用於存儲對象。
- 存儲: hset key field value
127.0.0.1:6379> hset myhash username lisi
(integer) 1
127.0.0.1:6379> hset myhash password 123
(integer) 1- 獲取:
- hget key field: 獲取指定的field對應的值
127.0.0.1:6379> hget myhash username
“lisi”- hgetall key:獲取所有的field和value
127.0.0.1:6379> hgetall myhash
1) “username”
2) “lisi”
3) “password”
4) “123”- 刪除: hdel key field
127.0.0.1:6379> hdel myhash username
(integer) 1
- 列表類型 list:可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)
- 添加:
- lpush key value: 將元素加入列表左表
- rpush key value:將元素加入列表右邊
127.0.0.1:6379> lpush myList a
(integer) 1
127.0.0.1:6379> lpush myList b
(integer) 2
127.0.0.1:6379> rpush myList c
(integer) 3- 獲取:
lrange key start end :範圍獲取
127.0.0.1:6379> lrange myList 0 -1
1) “b”
2) “a”
3) “c”- 刪除:
- lpop key: 刪除列表最左邊的元素,並將元素返回
- rpop key: 刪除列表最右邊的元素,並將元素返回
- 集合類型 set : 不允許重複元素-------集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。
- 存儲:sadd key value--------第二次插入的重複元素將被忽略。
127.0.0.1:6379> sadd myset a
(integer) 1
127.0.0.1:6379> sadd myset a
(integer) 0- 獲取:smembers key:獲取set集合中所有元素
127.0.0.1:6379> smembers myset
1) “a”- 刪除:srem key value:刪除set集合中的某個元素
127.0.0.1:6379> srem myset a
(integer) 1
- 有序集合類型 sortedset:不允許重複元素,且元素有順序.每個元素都會關聯一個double類型的分數。redis正是通過分數來爲集合中的成員進行從小到大的排序。有序集合的成員是唯一的,但分數(score)卻可以重複。---------排行榜
- 存儲:zadd key score value
127.0.0.1:6379> zadd mysort 60 zhangsan
(integer) 1
127.0.0.1:6379> zadd mysort 50 lisi
(integer) 1
127.0.0.1:6379> zadd mysort 80 wangwu
(integer) 1- 獲取:zrange key start end [withscores]
127.0.0.1:6379> zrange mysort 0 -1
1) “lisi”
2) “zhangsan”
3) “wangwu”
127.0.0.1:6379> zrange mysort 0 -1 withscores
1) “zhangsan”
2) “60”
3) “wangwu”
4) “80”
5) “lisi”
6) “500”- 刪除:zrem key value
127.0.0.1:6379> zrem mysort lisi
(integer) 1
- 通用命令
1. keys * : 查詢所有的鍵(可以換成正則表達式)
2. type key : 獲取鍵對應的value的類型
3. del key:刪除指定的key value
4. 持久化
- redis是一個內存數據庫,當redis服務器重啓,獲取電腦重啓,數據會丟失,我們可以將redis內存中的數據持久化保存到硬盤的文件中。
- redis持久化機制:
- RDB:默認方式,不需要進行配置,默認就使用這種機制
在一定的間隔時間中,檢測key的變化情況,然後持久化數據(對性能影響低)
- 編輯redis.windwos.conf文件
# after 900 sec (15 min) if at least 1 key changed
save 900 1
# after 300 sec (5 min) if at least 10 keys changed
save 300 10
# after 60 sec if at least 10000 keys changed
save 60 10000- 重新啓動redis服務器,並指定配置文件名稱
D:\JavaWeb2018\day23_redis\資料\redis\windows-64\redis-2.8.9>redis-server.exe redis.windows.conf
- AOF:日誌記錄的方式,可以記錄每一條命令的操作。可以每一次命令操作後,持久化數據
編輯redis.windwos.conf文件
appendonly no(關閉aof) --> appendonly yes (開啓aof)
# appendfsync always : 每一次操作都進行持久化
appendfsync everysec : 每隔一秒進行一次持久化
# appendfsync no : 不進行持久化
5. Java客戶端 Jedis
- Jedis: 一款java操作redis數據庫的工具.
- 使用步驟:
- 下載jedis的jar包
- 使用
- Jedis操作各種redis中的數據結構
- 字符串類型 string
set
get
代碼:
public class Demo {
public static void main(String[] args) {
//1. 獲取連接
Jedis jedis = new Jedis("localhost",6379);
//2. 操作
//存儲
jedis.set("username","zhangsan");
//可以使用setex()方法存儲可以指定過期時間的 key value
jedis.setex("activecode",20,"hehe");//將activecode:hehe鍵值對存入redis,並且20秒後自動刪除該鍵值對
//獲取
String username = jedis.get("username");
System.out.println(username);
//3. 關閉連接
jedis.close();
}
}
- 哈希類型 hash : map格式
hset
hget
hgetAll
- 列表類型 list : linkedlist格式。支持重複元素
lpush / rpush
lpop / rpop
lrange start end : 範圍獲取
public class Demo {
public static void main(String[] args) {
//1. 獲取連接
Jedis jedis = new Jedis("localhost",6379);
//2. 操作
//存儲
jedis.lpush("mylist","a","b","c");//從左邊存
jedis.rpush("mylist","a","b","c");//從右邊存
//獲取
List<String> mylist = jedis.lrange("mylist", 0, -1);
System.out.println(mylist);
//3. 關閉連接
jedis.close();
}
}
- 集合類型 set : 不允許重複元素
sadd
smembers:獲取所有元素
- 有序集合類型 sortedset:不允許重複元素,且元素有順序
zadd
zrange
- jedis連接池: JedisPool
使用:
- 創建JedisPool連接池對象
- 調用方法 getResource()方法獲取Jedis連接
public class Demo {
public static void main(String[] args) {
//0.創建一個配置對象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);
//1.創建Jedis連接池對象
JedisPool jedisPool = new JedisPool();
//2.獲取連接
Jedis jedis = jedisPool.getResource();
//3. 使用
jedis.set("username","lisi");
//4. 關閉 歸還到連接池中
}
}
- 連接池工具類
/**
* JedisPool工具類
* 加載配置文件,配置連接池的參數
* 提供獲取連接的方法
*/
public class JedisPoolUtils {
private static JedisPool jedisPool;
static {
//讀取配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//獲取數據,設置到JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));
//初始化JedisPool
jedisPool = new JedisPool(config,properties.getProperty("host"),
Integer.parseInt(properties.getProperty("port")));
}
/**
* 獲取連接方法
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
案例:
案例需求:
- 提供index.html頁面,頁面中有一個省份 下拉列表
- 當 頁面加載完成後 發送ajax請求,加載所有省份
- 注意:使用redis緩存一些不經常發生變化的數據。
數據庫的數據一旦發生改變,則需要更新緩存。
- 數據庫的表執行 增刪改的相關操作,需要將redis緩存數據情況,再次存入
- 在service對應的增刪改方法中,將redis數據刪除。
前端頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>findporvince</title>
<script src="js/jquery-2.1.0.min.js"></script>
<script>
$(function () {
//發送ajax請求,加載所有省份數據
$.post("FindProvinceServlet",function (data) {
var province = $("#province");
//[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"廣州"},{"id":4,"name":"陝西"}]
//2.遍歷json數組
$(data).each(function () {
var option = "<option name='"+ this.id +"'>"+this.name+"</option>"
province.append(option);
})
},"json")
})
</script>
</head>
<body>
<select id="province">
<option>----請選擇省份----</option>
</select>
</body>
</html>
Province類
public class Province {
private int id;
private String name;
......
}
Service層
public interface IProvinceService {
public String findAll();
}
public class ProvinceServiceImpl implements IProvinceService {
private IProvinceDao provinceDao = new ProvinceDaoImpl();
@Override
public String findAll() {
//1.先從redis中查詢數據
//1.1獲取redis客戶端連接
Jedis jedis = JedisPoolUtils.getJedis();
String province = jedis.get("province");
//2判斷 province_json 數據是否爲null
if (province == null || province.length() == 0){
//redis中沒有數據
System.out.println("redis中沒數據,查詢數據庫...");
//2.1從數據中查詢
List<Province> list = provinceDao.findAll();
ObjectMapper mapper = new ObjectMapper();
//2.2將list序列化爲json
try {
province = mapper.writeValueAsString(list);
jedis.set("province",province);
} catch (JsonProcessingException e) {
e.printStackTrace();
}finally {
jedis.close();
}
//2.3 將json數據存入redis
}
System.out.println(province);
return province;
}
}
Dao層
public interface IProvinceDao {
public List<Province> findAll();
}
public class ProvinceDaoImpl implements IProvinceDao {
private JdbcTemplate template = new JdbcTemplate(JDBCUtil.getDataSource());
@Override
public List<Province> findAll() {
String sql = "select * from province";
List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
return list;
}
}
FindProvinceServlet
package com.xzzz.web.serbvlet;
import com.xzzz.service.IProvinceService;
import com.xzzz.service.impl.ProvinceServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/FindProvinceServlet")
public class FindProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1.調用service查詢
IProvinceService provinceService = new ProvinceServiceImpl();
String json = provinceService.findAll();
//2.響應結果
response.setContentType("application/json;charset = utf-8");
response.getWriter().write(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}