redis是一個高可用,可持久化,基於內存的強大非關係型數據庫,今天做個記錄利用redis做一個簡單的多實例緩存系統並實現主從複製。
首先在linux上配置多實例redis服務以redis5.0.5爲例:
- 進入redis目錄
- vim redis.conf編輯配置文件
- set number顯示行號
- 69行處加上#禁掉:#bind 127.0.0.1,這樣外網可以範文服務器的ip
- 88行改爲no,關閉保護模式,不需要密碼登錄
- 92行設置端口。默認6379就好
- 114行設置爲timeout 3600,客戶端空閒1小時就斷開連接
- 137行改爲 daemonize yes,redis服務後臺啓動
- 159行pidfile /var/run/redis_6379.pid,指定pid文件與端口相同
- 172行指定日誌文件logfile “log6379.log”,與端口相同
- 254行指定持久化文件dbfilename dump6379.rdb,與端口相同
- 複製配置文件分別取名redis6380.conf,redis6381.conf
- 利用vim 的替換命令:%s/6379/6380/g,分別改爲對應的端口號
- 啓動redis-server redis.conf,redis-server redis6380.conf,redis-server redis6381.conf
- 查看進程:ps -ef|grep redis
可以看出單臺服務器啓動了三個節點的redis服務,每一個節點負責一部分數據
新建一個商品類:
public class Product {
// 根據數據庫表格結構類型,完成屬性封裝
// 定義了封裝持久層對象的駝峯命名
private Integer productId;
private String productName;
private Double productPrice;
private Integer productNum;
private String productDescription;
public Product() {
super();
// TODO Auto-generated constructor stub
}
public Product(Integer productId, String productName, Double productPrice, Integer productNum,
String productDescription) {
super();
this.productId = productId;
this.productName = productName;
this.productPrice = productPrice;
this.productNum = productNum;
this.productDescription = productDescription;
}
@Override
public String toString() {
return "Product [productId=" + productId + ", productName=" + productName + ", productPrice=" + productPrice
+ ", productNum=" + productNum + ", productDescription=" + productDescription + "]";
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Double getProductPrice() {
return productPrice;
}
public void setProductPrice(Double productPrice) {
this.productPrice = productPrice;
}
public Integer getProductNum() {
return productNum;
}
public void setProductNum(Integer productNum) {
this.productNum = productNum;
}
public String getProductDescription() {
return productDescription;
}
public void setProductDescription(String productDescription) {
this.productDescription = productDescription;
}
}
建立數據庫表
CREATE TABLE IF NOT EXISTS product (
productId INT PRIMARY KEY AUTO_INCREMENT,
productName VARCHAR (20) NOT NULL,#非空
productPrice DOUBLE NOT NULL,
productNum INT NOT NULL,#唯一約束,會建立唯一索引
productDescription TINYTEXT
)ENGINE=INNODB DEFAULT CHARSET=utf8 ;
手動寫入一些數據
配置文件配置
#redis-多實例配置
redis.nodes=192.168.80.78:6379,192.168.80.78:6380,192.168.80.78:6381
#資源池中的最大連接數,默認爲8,設置爲0沒有限制
redis.maxTotal=200
#資源池允許的最大空閒連接數,設 0 爲沒有限制,默認爲8
redis.maxIdle=8
#資源池確保的最少空閒連接數,默認爲0
redis.minIdle=3
#當資源池連接用盡後,調用者的最大等待時間(單位爲毫秒),-1(表示永不超時)
redis.maxWaitMillis=5000
配置類配置分片連接池
@Configuration
@ConfigurationProperties(prefix = "redis")
@Lazy
public class RedisConfig {
// 根據前綴讀取數據,私有屬性名稱,必須和
// properties中的值相同
private String nodes;
private Integer maxTotal;
private Integer maxIdle;
private Integer minIdle;
private Long maxWaitMillis;
@Bean
public ShardedJedisPool initJedisPool() {
// 利用本類中讀取的屬性,創建連接池對象
// 先做一個config對象
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 資源池允許的最大空閒連接數,設 0 爲沒有限制,默認爲8
config.setMaxIdle(maxIdle);
// 資源池中的最大連接數,默認爲8,設置爲0沒有限制
config.setMaxTotal(maxTotal);
// 當資源池連接用盡後,調用者的最大等待時間(單位爲毫秒),-1(表示永不超時)
config.setMaxWaitMillis(maxWaitMillis);
// 資源池確保的最少空閒連接數,默認爲0
config.setMinIdle(minIdle);
// config.setMinIdle(minIdle);
// 解析nodes,生成一個list對象
// 準備一個空內容
List<JedisShardInfo> infoList = new ArrayList<JedisShardInfo>();
String[] node = nodes.split(",");// {"192.168.80.129:6379","1192.168.80.129:6380","192.168.80.129:6381"}
for (String hostAndPort : node) {
String host = hostAndPort.split(":")[0];
int port = Integer.parseInt(hostAndPort.split(":")[1]);
infoList.add(new JedisShardInfo(host, port));
}
// list,config,構造連接池對象返回
return new ShardedJedisPool(config, infoList);
}
public String getNodes() {
return nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
public Integer getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(Integer maxTotal) {
this.maxTotal = maxTotal;
}
public Integer getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Long getMaxWaitMillis() {
return maxWaitMillis;
}
public void setMaxWaitMillis(Long maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}
public RedisConfig() {
super();
}
public RedisConfig(String nodes, Integer maxTotal, Integer maxIdle, Integer minIdle, Long maxWaitMillis) {
super();
this.nodes = nodes;
this.maxTotal = maxTotal;
this.maxIdle = maxIdle;
this.minIdle = minIdle;
this.maxWaitMillis = maxWaitMillis;
}
}
寫一個工具類生成連接池和jedis對象負責redis連接,實現增刪改查
@Component
public class RedisCumUtils {
@Autowired
private ShardedJedisPool pool;
//query,addOrUpdate,delete,isExists
public String query(String key){
//從池子中獲取jedis對象負責連接
ShardedJedis jedis = pool.getResource();
try{
return jedis.get(key);
}catch(Exception e){
//異常處理邏輯
return null;
}finally{
//關閉連接
jedis.close();
}
}
public void addOrUpdate(String key,String value){
ShardedJedis jedis = pool.getResource();
try{
jedis.set(key, value);
}catch(Exception e){
//異常處理邏輯
}finally{
jedis.close();
}
}
public void delete(String key){
ShardedJedis jedis = pool.getResource();
try{
jedis.del(key);
}catch(Exception e){
//異常處理邏輯
}finally{
jedis.close();
}
}
public Boolean isExist(String key){
ShardedJedis jedis = pool.getResource();
return jedis.exists(key);
}
}
控制層代碼
//根據商品id查詢對象數據
@ResponseBody
@GetMapping(value = "product/{productId}")
public Product queryByid(@PathVariable Integer productId ) {
//控制層無需處理數據調用,業務邏輯
Product product=demoservice.queryById(productId);
return product;
}
服務層代碼:
@Autowired
RedisCumUtils jedis;
public Product queryById(Integer productId) {
// 創建一個連接對象使用來操作redis緩存技術
// 通過注入進來的pool獲取數據連接資源ShardedJedis
// ShardedJedis jedis=pool.getResource();
// 生成當前業務邏輯的key值,業務信息+productId
String productKey = "product_" + productId;
// 想辦法將product序列化成String的json字符串,使用jackson的api
ObjectMapper mapper = new ObjectMapper();
try {
if (jedis.isExist(productKey)) {
//JSON字符串解析反序列化
String json=jedis.query(productKey);
Product product=mapper.readValue(json, Product.class);
System.out.println(product);
return product;
}else{//商品key不存在,需要訪問數據庫,將數據存放redis
Product product = demoMapper.queryById(productId);
//cong product對象映射到json字符串 writeValueAsString"":""
String json=mapper.writeValueAsString(product);
// System.out.println(json);//{"":"","":"","":""}
//將json存儲到redis中
jedis.addOrUpdate(productKey, json);
return product;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
持久層代碼:
Product queryById(@Param(value="productId")Integer productId);
mybtis配置
<select id="queryById" parameterType="Integer" resultType="Product">
SELECT productId, productName,productNum, productPrice,productDescription FROM Product WHERE productId=#{productId};
</select>
http://localhost:8093/product/99 訪問
控制檯沒有打印出99號商品的信息
再次訪問http://localhost:8093/product/99
控制檯已經打印出99號商品的信息說明查詢了緩存
商品新增邏輯
控制層代碼
//新增商品數據到數據庫
Product product=new Product(null,"飛科剃鬚刀",654.0,654150,"就是快");
@ResponseBody
@RequestMapping(value = "product/saveProduct")
public int saveProduct(){
try{
demoservice.saveProduct(product);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
服務層和持久層代碼
//新增商品數據
public void saveProduct(Product product) {
demoMapper.saveProduct(product);
}
void saveProduct(Product product);
mybtis配置
<insert id="saveProduct" parameterType="Product">
insert into product (
productId,
productName,
productNum,
productPrice,
productDescription
) values(
#{productId},
#{productName},
#{productNum},
#{productPrice},
#{productDescription}
)
執行http://localhost:8093/product/saveProduct
看下數據庫插入結果
商品更新操作
當查詢緩存時,如果數據庫已經對數據進行修改,這個時候讀取的緩存和數據庫數據不一致,產生髒讀,解決就是跟新數據庫同時覆蓋redis中的緩存數據,思路就是跟新時產生一個跟新鎖,查詢時先判斷是否有更新鎖,如果有直接查數據庫,沒有從緩存中返回
修改服務層代碼並添加更新邏輯
@Autowired
RedisCumUtils jedis;
public Product queryById(Integer productId) {
// 定義更新鎖的key值
String productLockKey = "product_" + productId + ".lock";
// 生成當前業務邏輯的key值,業務信息+productId
String productKey = "product_" + productId;
// 想辦法將product序列化成String的json字符串,使用jackson的api
ObjectMapper mapper = new ObjectMapper();
try {
if (jedis.isExist(productLockKey)) {
// 說明有人正在更新,不能操作緩存
return demoMapper.queryById(productId);
} else if (jedis.isExist(productKey)) {
// JSON字符串解析反序列化
String json = jedis.query(productKey);
Product product = mapper.readValue(json, Product.class);
System.out.println(product);
return product;
} else {// 商品key不存在,需要訪問數據庫,將數據存放redis
Product product = demoMapper.queryById(productId);
// cong product對象映射到json字符串 writeValueAsString"":""
String json = mapper.writeValueAsString(product);
// 將json存儲到redis中
jedis.addOrUpdate(productKey, json);
return product;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/新增商品數據
public void saveProduct(Product product) {
demoMapper.saveProduct(product);
}
public void updateProductById(Product product) {
//更新鎖添加到redis
String productLockKey="product_"+product.getProductId()+".lock";
//定義緩存的key
String productKey="product_"+product.getProductId();
//添加鎖//可以定義鎖的超時
jedis.addOrUpdate(productLockKey, "");
//刪除已有的緩存
jedis.delete(productKey);
demoMapper.updateProduct(product);
//釋放鎖
jedis.delete(productLockKey);
}
控制層
@ResponseBody
@RequestMapping("product/updateProduct")
public Integer updateProduct(Product product){
try{
Product product1=new Product(1,"波導手機",565654.0,220,"就是好看得不行");
demoservice.updateProductById(product1);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
持久層
void updateProduct(Product product);
mybatis配置
<update id="updateProduct" parameterType="Product">
update product set
productId = #{productId},
productName = #{productName},
productNum =
#{productNum},
productPrice =
#{productPrice},
productDescription =
#{productDescription}
where
productId=#{productId}
</update>
第一行數據
執行http://localhost:8093/product/updateProduct
第一行數據
查詢第一行數據
依然能查到而且是最新數據
可以註釋掉服務層
//釋放鎖
//jedis.delete(productLockKey);
會發現每次查詢都是從數據庫獲得
未完待續…