redis多實例緩存系統實現

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);
會發現每次查詢都是從數據庫獲得

未完待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章