数据库优化(HA+redis)

1 数据库同步

1.1 数据库冷备份

手动的将数据库文件,定期进行转储.
缺点:
1.效率低
2.有可能丢失数据
原因: 数据库冷备份 是公司恢复数据最后有效的手段.

1.2 数据库热备份

备份原理:
1.当数据库修改时,会将修改的信息,写入二进制日志文件中(二进制文件默认是关闭的).
2.当二进制日志文件中有数据时,数据库从库会通过IO线程读取二进制文件信息.
3.IO线程将读到的数据写到中继日志中.
4.Sql线程将中继日志中的文件 写到从数据库中,最终实现数据库主从同步.
在这里插入图片描述

1.3 修改数据库主库配置

1.3.1 搭建策略

129当作主库,130当作从库

1.3.2 修改二进制日志文件

1).编辑二进制文件位置
在这里插入图片描述
2).修改二进制文件内容
在这里插入图片描述
3).重启数据库,检查二进制日志文件是否有效
在这里插入图片描述

1.3.3 修改数据库从库配置

在这里插入图片描述
重启数据库检查二进制日志
在这里插入图片描述

1.4 实现数据库主从同步

1.4.1 确定主库状态

在这里插入图片描述

1.4.2 配置主从挂载

/*我是130  从库  IP/PORT/USER/PASSWROD/二进制日志/pos*/
CHANGE MASTER TO MASTER_HOST="192.168.126.129",
MASTER_PORT=3306,
MASTER_USER="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=245;

/*开启主从服务*/
START SLAVE;	

/*检查主从服务状态*/
SHOW SLAVE STATUS;

/*如果启动不成功 执行如下步骤  检查最后几项的报错信息 
  之后修改配置文件. 关闭主从服务.之后重新执行挂载命令
*/
STOP SLAVE;

1.4.3 数据库主从测试

在主库中添加数据库表,检查从库是否同步.
在这里插入图片描述

2 数据库高可用(HA)

2.1 实现数据库读写分离/负载均衡

在这里插入图片描述

2.2 Mycat代理

2.2.1 Mycat 介绍

阿里的产品
在这里插入图片描述
在这里插入图片描述

2.3 Mycat配置

2.3.1 上传安装包解压

在这里插入图片描述
命令
解压:tar -xvf Mycat####
压缩包移到software :mv ### software/

2.3.2 编辑Server.xml

说明: 用户与代理数据库之间的链接,通过Server.xml进行配置.
1).默认端口
在这里插入图片描述
在这里插入图片描述

2.3.3 编辑schemas.xml

说明:该文件表示代理与数据库的配置 HOST:PORT:用户名:密码:数据库名称

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	
	<!--name属性是自定义的  dataNode表示数据库的节点信息  jtdb表示逻辑库-->
	<schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/>

	<!--定义节点名称/节点主机/数据名称-->
	<dataNode name="jtdb" dataHost="localhost1" database="jtdb" />
		<!--参数介绍 UTF-8 中文报错  使用单行注释 -->
		<!--balance 0表示所有的读操作都会发往writeHost主机 -->  
		<!--1表示所有的读操作发往readHost和闲置的主节点中-->
		<!--writeType=0 所有的写操作都发往第一个writeHost主机-->	
		<!--writeType=1 所有的写操作随机发往writeHost中-->
		<!--dbType 表示数据库类型 mysql/oracle-->
		<!--dbDriver="native"  固定参数 不变-->
		<!--switchType=-1 表示不自动切换, 主机宕机后不会自动切换从节点-->
		<!--switchType=1  表示会自动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点-->
		<!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题-->

		
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select 1</heartbeat>

		<!--配置第一台主机主要进行写库操作,在默认的条件下Mycat主要操作第一台主机在第一台主机中已经实现了读写分离.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担部分压力.
		-->
		<writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root">
			<!--读数据库1-->
			<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
			<!--读数据库2-->
			<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
		</writeHost>

			<!--定义第二台主机 由于数据库内部已经实现了双机热备.-->
			<!--Mycat实现高可用.当第一个主机137宕机后.mycat会自动发出心跳检测.检测3次.-->
			<!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机继续为用户提供服务.-->
			<!--如果137主机恢复之后则处于等待状态.如果141宕机则137再次持续为用户提供服务.-->
			<!--前提:实现双机热备.-->
		
		<!--<writeHost host="hostM2" url="192.168.126.130:3306" user="root" password="root">
			
			<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
			
			<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
		</writeHost>-->
		
	</dataHost>
</mycat:schema>

2.3.4 上传安装文件

文件上传到指定的位置: /usr/local/src/mycat/conf
在这里插入图片描述

2.3.5 启动Mycat

./mycat start
Mycat命令
在这里插入图片描述

2.3.6 报错检查

查看日志
在这里插入图片描述

2.3.7 负载均衡测试

将从数据库信息,手动的进行修改. 效果:主库和从库的数据暂时不一致(存在风险)
在这里插入图片描述
在这里插入图片描述

2.4 实现数据库双机热备

2.4.1 原理说明

说明: 实现数据库高可用的前提条件是实现主从的备份.

之前的操作:
主库 :192.168.126.129
从库 : 192.168.126.130
现在的配置:
主库 :192.168.126.130
从库 : 192.168.126.129
在这里插入图片描述

2.4.2 实现双主模式配置

1).检查主库状态
在这里插入图片描述
2).实现主从配置

show MASTER status


# 我是129 之前是主库,今天当从库
CHANGE MASTER to MASTER_HOST="192.168.126.130",
MASTER_PORT=3306,
MASTER_user="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=700;

#启动主从服务
start slave	

#检查状态
show slave status;

在这里插入图片描述

2.5 实现数据库高可用

2.5.1 修改schema.xml配置文件

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	
	<!--name属性是自定义的  dataNode表示数据库的节点信息  jtdb表示逻辑库-->
	<schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/>

	<!--定义节点名称/节点主机/数据名称-->
	<dataNode name="jtdb" dataHost="localhost1" database="jtdb" />
		<!--参数介绍 UTF-8 中文报错  使用单行注释 -->
		<!--balance 0表示所有的读操作都会发往writeHost主机 -->  
		<!--1表示所有的读操作发往readHost和闲置的主节点中-->
		<!--writeType=0 所有的写操作都发往第一个writeHost主机-->	
		<!--writeType=1 所有的写操作随机发往writeHost中-->
		<!--dbType 表示数据库类型 mysql/oracle-->
		<!--dbDriver="native"  固定参数 不变-->
		<!--switchType=-1 表示不自动切换, 主机宕机后不会自动切换从节点-->
		<!--switchType=1  表示会自动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点-->
		<!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题-->

		
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select 1</heartbeat>

		<!--配置第一台主机主要进行写库操作,在默认的条件下Mycat主要操作第一台主机在第一台主机中已经实现了读写分离.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担部分压力.
		-->
		<writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root">
			<!--读数据库1-->
			<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
			<!--读数据库2-->
			<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
		</writeHost>

			<!--定义第二台主机 由于数据库内部已经实现了双机热备.-->
			<!--Mycat实现高可用.当第一个主机137宕机后.mycat会自动发出心跳检测.检测3次.-->
			<!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机继续为用户提供服务.-->
			<!--如果137主机恢复之后则处于等待状态.如果141宕机则137再次持续为用户提供服务.-->
			<!--前提:实现双机热备.-->
		
		<writeHost host="hostM2" url="192.168.126.130:3306" user="root" password="root">
			
			<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
			
			<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
		</writeHost>
		
	</dataHost>
</mycat:schema>

2.5.2 上传文件

1).停止mycat服务
在这里插入图片描述
2).删除配置文件
3).上传文件
将配置文件上传到指定的目录中
在这里插入图片描述
4).启动Mycat

2.5.3 数据库高可用的测试

1.首先应该将129的数据库宕机(关闭)
2.启动程序,通过数据库代理检查是否访问正确(查). 入库是否正常(写)
3.重启129的数据库. 检查数据是否真的同步了.

3 Redis缓存

3.1 系统优化策略

说明:引入缓存机制可以有效的降低用户访问物理设备的频次,从而提高响应速度.
在这里插入图片描述

3.2 如何设计缓存

1.缓存数据如何存储? 应该采用什么样的数据结构呢? K-V key的唯一性
2.缓存数据的容量大小 应该动态维护缓存数据,将不需要的数据提前删除. LRU算法/LFU算法/随机算法/TTL算法
3.缓存数据保存到内存中,但是内存的特点断电即擦除. 定期将内存数据持久化(写入磁盘中)
4.单台缓存服务器性能不足,所以一般需要搭建集群(实现高可用).
5.使用C语言开发.

3.3 Redis缓存服务

3.3.1 什么是Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

速度: 读: 11.2万/秒 写:8.6万/秒 卡夫卡:50万/秒

3.3.2 上传Redis

1).上传redis
2).解压redis服务
[root@localhost src]# tar -xvf redis-5.0.4.tar.gz

3).移动文件/修改文件名称
在这里插入图片描述

3.3.3 安装Redis

说明:在Redis根目录中执行如下命令(C语言写的)

1).make
2). make install
在这里插入图片描述

3.3.4 修改redis配置文件

修改redis根目录下的redis.conf文件
1).去除IP绑定
在这里插入图片描述
2).修改保护模式
在这里插入图片描述
3).开启后台启动
在这里插入图片描述

3.4 Redis服务器命令

说明: Redis服务在运行时,必须依赖于配置文件 redis.conf. 操作redis时最好在根目录中操作

1).启动redis
redis-server redis.conf
在这里插入图片描述
2).进入redis客户端
redis-cli -p 6379
ctrl + c 退出客户端
在这里插入图片描述
3).关闭redis服务器
kill pid
或 redis-cli -p 6379 shutdown
补充说明: 如果操作的端口号是默认端口6379 则可以省略不写.

4 SpringBoot整合Redis

4.1 整合入门案例

导包

<!--spring整合redis redisTemplate Spring封装jedis高级API-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>

4.1.1 测试基本命令

package com.jt.test;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

//@SpringBootTest
public class TestRedis {

    //链接服务: IP地址:端口
    @Test
    public void testSetGet() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.flushAll();   //先清空redis缓存
        //1.存取redis
        jedis.set("key1", "天天向上");
        String value = jedis.get("key1");
        System.out.println(value);
        //2.判断key是否存在
        if(jedis.exists("key1")){
            jedis.set("key1", "好好学习,天天向上");
        }else{
            jedis.set("key1", "天天开心");
        }

        //3.为key添加超时时间
        jedis.expire("key1", 10);
        Thread.sleep(2000);
        System.out.println("剩余存活时间:"+jedis.ttl("key1"));

        //4.撤销剩余超时时间
        jedis.persist("key1");
        System.out.println("撤销成功!!");
    }
}

4.1.2 setNx/setEx/set命令

    /**
     * 需求: 如果数据不存在,则将数据修改
     */
    @Test
    public void testSetNx(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.set("key1", "aaa");
        //如果key不存在,则赋值
        jedis.setnx("key1", "测试方法");
        System.out.println(jedis.get("key1"));
    }

    /**
     * 需求: 实现超时时间的设定与赋值操作的原子性.
     * 考点: 为数据设定超时时间时,切记满足原子性要求.
     *      否则会出现key永远不能删除的现象
     */
    @Test
    public void testSetEx(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        /*jedis.set("key2", "bbb");
        jedis.expire("key2", 3);*/
        //???数据超时之后一定会被删除吗???  错的
        jedis.setex("key2", 10, "bbb");
    }

    /**
     * 需求: 如果数据存在时,才会修改数据,并且为数据添加超时时间10秒(原子性).
     * 参数说明:
     *      NX: 只有数据不存在时,才会赋值.
     *      XX: 只有数据存在时,才会赋值.
     *      EX: 秒
     *      PX: 毫秒
     */
    public void testSet(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
       /* if(jedis.exists("key3")){
            jedis.setex("key3",10 ,"ccc");
        }*/
        SetParams setParams = new SetParams();
        setParams.xx().ex(10);
        //将所有的操作采用原子性的方式进行控制
        jedis.set("key3", "ccc", setParams);
    }

4.1.2 测试hash

/**
     * HASH测试
     */
    @Test
    public void testHash(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.hset("person", "id", "100");
        jedis.hset("person", "name", "tomcat猫");
        System.out.println(jedis.hgetAll("person"));
    }

4.1.3 测试List

 /**
     * LIST集合测试
     */
    @Test
    public void testList(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.lpush("list", "1","2","3","4");
        String value = jedis.rpop("list");
        System.out.println(value);
    }

4.1.4 测试事务

/**
     * Set集合测试
     *  1. sadd  新增元素
     *  2. SCARD  获取元素数量
     *  3. SINTER key1 key2  获取元素的交集
     *  4. SMEMBERS set      获取集合元素
     *  demo自己补一下
     *  * */

    @Test
    public void testMulti(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //开启事务
        Transaction transaction = jedis.multi();
        try {
            transaction.set("a", "a");
            transaction.set("b", "b");
            //提交事务
            transaction.exec();
        }catch (Exception e){
            //回滚事务
            transaction.discard();
        }
    }

4.2 封装共性代码

4.2.1 编辑properties配置文件

在这里插入图片描述

4.2.2 编辑配置类

在这里插入图片描述

4.3 ObjectMapperUtil实现

4.3.1 业务需求

因为数据库取出来的数据是对象,要转化成JSON串存到缓存中,缓存的数据取出来是JSON格式,要转化成对象返回到控制层,所以需要些工具类封装代码方便使用,提高复用性。
说明:在业务中通常需要将业务对象转化为JSON数据.需要通过工具API进行手动的转化.

4.3.2 入门案例

public class TestObjectMapper {

    //objectMapper入门案例
    //2. 将JSON转化为对象
    @Test
    public void testObject() throws JsonProcessingException {
        //1. 将对象转化为JSON
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L)
                .setItemDesc("我是一个测试数据")
                .setCreated(new Date())
                .setUpdated(itemDesc.getCreated());
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(itemDesc);
        System.out.println(json);
        //2.将JSON转化为对象
        ItemDesc desc = objectMapper.readValue(json, ItemDesc.class);
        System.out.println(desc);
    }

    @Test
    public void testList() throws JsonProcessingException {
        List<ItemDesc> list = new ArrayList<>();
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L).setItemDesc("我是一个测试数据").setCreated(new Date()).setUpdated(itemDesc.getCreated());
        ItemDesc itemDesc2 = new ItemDesc();
        itemDesc2.setItemId(200L).setItemDesc("我是一个测试数据").setCreated(new Date()).setUpdated(itemDesc.getCreated());
        list.add(itemDesc);
        list.add(itemDesc2);
        //1. 将对象转化为JSON
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(list);
        System.out.println(json);
        //2.将JSON转化为对象
        List<ItemDesc> list2 = objectMapper.readValue(json,list.getClass());
        System.out.println(list2);
    }

}

4.3.3 编辑ObjectMapperUtil

public class ObjectMapperUtil {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static String toJSON(Object target){
        try {
            return MAPPER.writeValueAsString(target);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            //将检查异常转化为运行时异常.
            throw new RuntimeException(e);
        }
    }

    public static <T> T toObject(String json,Class<T> targetClass){
        try {
            return MAPPER.readValue(json, targetClass);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

4.4 实现商品分类缓存操作

4.4.1 编辑ItemController

 /**
     * 业务需求: 实现商品分类树形结构展现
     * url地址:   http://localhost:8091/item/cat/list
     * 参数:      id= 父级节点的ID
     * 返回值:    List<EasyUITree>
     */
    @RequestMapping("/list")
    public List<EasyUITree> findItemCatList(Long id){
        //暂时只查询一级商品分类信息
        long parentId = (id==null?0:id);
        //return itemCatService.findItemCatList(parentId);
        return itemCatService.findItemCatCache(parentId);
    }

4.4.2 编辑ItemService

@Service
public class ItemCatServiceImpl implements ItemCatService{

    @Autowired
    private ItemCatMapper itemCatMapper;
    @Autowired(required = false)  //类似于懒加载
    private Jedis jedis;
    /**
     * 1.根据parentId 查询子级目录信息
     * 2.获取ItemCatList集合,之后将List集合转化为VOList集合
     * 3.返回数据
     * @param parentId
     * @return
     */
    @Override
    public List<EasyUITree> findItemCatList(long parentId) {
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", parentId);
        List<ItemCat> catList = itemCatMapper.selectList(queryWrapper);

        //2 list集合转化
        List<EasyUITree> treeList = new ArrayList<>(catList.size());
        for (ItemCat itemCat : catList){
            long id = itemCat.getId();
            String text = itemCat.getName();
            String state = itemCat.getIsParent()?"closed":"open"; //是父级,默认应该closed
            EasyUITree easyUITree = new EasyUITree(id, text, state);
            treeList.add(easyUITree);
        }
        return treeList;
    }


    /**
     * 实现步骤:
     *      1.先定义key     ITEM_CAT_PARENT::0
     *      2.先查询缓存
     *          有  true    获取缓存数据之后,将JSON转化为对象 之后返回
     *          没有 false   应该查询数据库, 之后将数据保存到redis中. 默认30天超时.
     * * @param parentId
     * @return
     */
    @Override
    public List<EasyUITree> findItemCatCache(long parentId) {
        Long startTime = System.currentTimeMillis();
        List<EasyUITree> treeList = new ArrayList<>();
        //1.定义key
        String key = "ITEM_CAT_PARENT::"+parentId;
        //2.从缓存中获取对象
        if(jedis.exists(key)){
            //存在  直接获取缓存数据,之后转化为对象
            String json = jedis.get(key);
            treeList =  ObjectMapperUtil.toObject(json,treeList.getClass());
            System.out.println("查询redis缓存,获取数据");
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:"+(endTime-startTime)+"毫秒");
        }else{
            //不存在  应该先查询数据库,之后将数据保存到缓存中.
            treeList = findItemCatList(parentId);
            String json = ObjectMapperUtil.toJSON(treeList);
            jedis.setex(key, 7*24*60*60, json);
            System.out.println("查询数据库获取结果");
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:"+(endTime-startTime)+"毫秒");
        }

        return treeList;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36844475/article/details/110829108
今日推荐