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);
会发现每次查询都是从数据库获得

未完待续…

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