linux安装redis
安装rz和sz命令
yum install lrzsz
安装gcc
新建目录mkdir software
通过rz将redis的tar包拖到software下
解压 tar -xf 并进入
yum install gcc-c++
make && make install编译安装
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);
会发现每次查询都是从数据库获得
未完待续…