0%

deom框架

三个module,一个提供api,一个提供api的实现,一个调用方。工程结构如下图所示。
工程结构

一、api

提供接口API

public interface ServiceA {
    String greet(String name);
}

二、服务提供方ServiceA

(1)pom.xml配置

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
        <version>2.1.2.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-context</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-dubbo</artifactId>
        <version>2.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.liyyao.demo</groupId>
        <artifactId>demo-dubbo-nacos-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        <version>2.1.1.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-context</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-context</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>  

(2)application.properties配置

spring.application.name=demo-dubbo-nacos-ServiceA
dubbo.scan.base-packages=com.liyyao.demo.dubbo.nacos
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.registry.address=spring-cloud://localhost
spring.cloud.nacos.discovery.server-addr=101.133.233.66:8848,101.133.175.55:8848,101.133.172.57:8848  

(3)api接口的实现

@Service(
        version = "1.0.0",
        interfaceClass = ServiceA.class,
        cluster = "failfast",
        loadbalance = "roundrobin"
)
public class ServiceAImpl implements ServiceA {
    public String greet(String name) {
        return "hello, " + name;
    }
}  

(4)启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}  

三、服务消费方ServiceB

(1)pom.xml配置

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
        <version>2.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-dubbo</artifactId>
        <version>2.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.zhss.demo</groupId>
        <artifactId>demo-dubbo-nacos-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        <version>2.1.1.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-context</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-context</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.5</version>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>  

(2)application.properties配置

spring.application.name=demo-dubbo-nacos-ServiceB
dubbo.cloud.subscribed-services=demo-dubbo-nacos-ServiceA
dubbo.scan.base-packages=com.liyyao.demo.dubbo.nacos
spring.cloud.nacos.discovery.server-addr=101.133.233.66:8848,101.133.175.55:8848,101.133.172.57:8848  

(3)服用ServiceA的方法

@RestController
public class TestController {
    @Reference(version = "1.0.0",
            interfaceClass = ServiceA.class,
            cluster = "failfast")
    private ServiceA serviceA;

    @GetMapping("/greet")
    public String greet(String name) {
        return serviceA.greet(name);
    }
}  

(4)启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}  

四、测试

启动成功后,打开浏览器,输入localhost:8080/great?name=lee可访问成功(name的参数输入什么,页面中显示什么)如下图所示。
测试结果

一、解决什么问题?

(1)数据量大
(2)需要水平切分
(3)一个schema上有多个字段的查询需求

举例:订单业务
Order(oid, info_detail)
T(buyer_id, seller_id, oid)
数据量大了怎么办?一个表一个库是存不下的,这个时候往往需要进行水平切分。订单表很容易进行水平切分,根据订单id进行表切分,查询的时候可以根据订单id直接定位到它水平切分到哪个库或者哪个表里面。但是关系表怎么切分呢?有买家id、卖家id,如果通过买家id来水平切分,保证同一个买家id的数据在一个库里或一个表里,根据买家id可以定位到这个买家有多少个订单id,这里如果要用卖家id来查询的话,就需要遍历多个库了;反之如果用卖家id来进行分库分表,同一个卖家的订单关系在一个库或一个表里,此时如果用买家id来查询订单,也需要遍历多个库或多个表。所以不管用买家id还是卖家id来进行水平切分,都有另外一种业务是无法满足的,如果要满足就需要扫描多库,在库的数量非常多数据量非常大的时候,扫描多库的性能是非常非常的低。

二、如何解决?

如果用大学数据库学的范式来解决,几乎是无解的,所以在互联网的某些场景之下,只有逆范式或反范式来设计表结构,进行数据冗余才可以满足在多个业务场景下多个查询条件下的水平切分的需求。

冗余表
Order(oid, info_detail)
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)

三、如何冗余?

(1)服务同步冗余
在进行表数据插入时,同时插入到T1,T2两张表中
优点:1.不复杂; 2.不一致性概率低
缺点:1.因为要插入两张表数据,处理业务时间增加; 2.仍然可能出现不一致

(2)服务异步冗余
增加一个消息队列,当插入T1表后,向消息队列中发送一个消息,然后由另外一个服务将数据插入到T2表中
优点:访问一次数据库,与不冗余数据时时间相同
缺点:1.复杂度增加,增加一个MQ; 2.消息发送成功不等于T2插入成功,此时查询T2会查询不到数据,但这个时间比较短,业务上往往是可以接受的; 3.不在一个分布式事务里,会出现数据不一致性

(3)线下异步冗余
添加一个bin log,然后调用服务进行T2表的数据插入
优点:两次写操作解耦
缺点:1.有延时,可能在T2中查询不到数据; 2.会出现数据不一致性问题

四、“原子性事务”,正向表和反向表谁先操作?

如果原子性被破坏,不一致出现,谁先做对业务的影响较小,就谁先执行

五、如何保证一致性?

在大数据、高并发、延迟敏感的业务中,并不能实时保证一致性,而是尽可能的发现数据不一致性,然后保证最终一致性。

六、如何保证一致性?

(1)全量数据扫描
写一个离线程序,定时每天检查一次T1和T2表中数据是否一致,不一致的话进行补偿,使数据一致
优点:解耦
缺点:每天都需要大量检测,已经检测过的数据会重复检测,需要耗费的时间长,会导致数据不一致性的时间长

(2)增量日志扫
服务插入T1或T2表后向T1日志或T2日志中写一条日志数据,然后写一个离线的程序,对日志进行检测,发现不一致则进行补偿,使数据一致
优点:1.不会重复检测数据; 2.检测周期缩短; 3.线上影响比较低
缺点:时效性还是不高

(3)实时消息对检测
增加一个消息队列,插入T1成功后就向消息队列中发送一条消息,插入T2d成功后,就向消息队列中发送一条消息,根据经验,5秒内能收到2条消息,如果未收到消息,则检测数据库数据是否一致
缺点:复杂度高

ubuntu 16.04及以上可用。

1. 准备4台linux服务器

  • 准备阿里云服务器4台,3台用做nacos集群,1台用做mysql数据库
  • 配置阿里云服务器网络安全组,打开入网端口(8848)

2. 安装mysql

(1)下载并安装MySQL官方的Yum Repository, Mysql版本 5.7.14

# wget -i -c https://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
# yum -y install mysql57-community-release-el7-10.noarch.rpm

(2)安装MySQL 服务器

# yum -y install mysql-community-server

(3)MySQL数据库设置

  • 首先启动MySQL

    # systemctl start mysqld.service

  • 查看MySQL运行状态

    # systemctl status mysqld.service

  • 找root用户密码

    # grep “password” /var/log/mysqld.log

  • 进入数据库

    # mysql -uroot -p

  • 修改数据库密码

    mysql> SET PASSWORD = PASSWORD(‘12345678’);
    mysql> ALTER USER ‘root’@’localhost’ PASSWORD EXPIRE NEVER;
    mysql> FLUSH PRIVILEGES;

  • 如果提示密码不符合策略,做如下操作再设置密码

    mysql> set global validate_password_policy=0;
    mysql> set global validate_password_length=1;

  • 重新登录数据库,设置远程连接

    mysql> use mysql;
    mysql> update user set host = ‘%’ where user = ‘root’;
    mysql> FLUSH PRIVILEGES;

  • 进行远程连接测试

    本地进行连接测试

  • 为firewalld添加开放端口

    添加mysql端口3306
    # systemctl start firewalld
    # firewall-cmd –zone=public –add-port=3306/tcp –permanent
    # firewall-cmd –reload

  • 更改mysql语言

    首先重新登录mysql,然后输入status,可以看到Server characterset: latinl,不是utf-8;
    出mysql, vi /etc/my.cnf新增下面代码
    character-set-server=utf8
    collation-server=utf8_general_ci
    保存成功后,重启mysql,然后输入status就会发现变化了

3. 安装Nacos

(1)准备3台服务器作nacos集群

配置hosts
# vi /etc/hosts
配置本机的hostname到ip地址的映射

(2)安装JDK

到ORACLE官网下载linux版本的jdk:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
上传jdk到三台服务器上,然后安装 # rpm -ivh jdk-8u131-linux-x64.rpm
配置jdk相关环境变量 # vi /etc/bashrc
export JAVA_HOME=/usr/java/jdk1.8.0_131
export PATH=$PATH:$JAVA_HOME/bin
source /etc/bashrc
测试jdc安装是否成功:java -version

(3)下载nacos-server-1.4版本的源码

# git clone https://github.com/alibaba/nacos.git
# cd nacos
# mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
# ls -al distribution/target/ 可以看到nacos-server-1.4.0-SNAPSHOT.tar.gz压缩包  

(4) nacos上传到服务器上

win命令可以用 scp nacos-server-1.4.0-SNAPSHOT.tar.gz root@192.168.xxx.xxx:/usr/local/xxx 上传到三台服务器上

(5)配置nacos

  • 解压nacos
    • 进入到nacos/conf目录下,重命名 cluster.conf.example, 去掉 example: mv cluster.conf.example cluster.conf, 然后编辑cluster.conf文件,配置三台机器的地址和端口号,默认端口号是8848
    • 配置数据库

      修改application.properties文件里面数据库配置
      spring.datasource.platform=mysql
      db.num=1
      db.url.0=xx, 替换成mysql数据库端口号及数据库
      db.user=xx
      db.password=xx

    • 打开8848端口

      开启防火墙 # systemctl start firewalld
      开放指定端口 # firewall-cmd –zone=public –add-port=8848/tcp –permanent
      重启防火墙 # firewall-cmd –reload

  • mysql导入nacos相关数据库表
    • 在下载的nacos源码里nacos\distribution\conf下有nacos-mysql.sql文件
  • 启动
    • 分别进行三台机器的bin目录下,执行startup.sh,检查logs目录下的start.out启动日志
    • 启动时如果startup.sh无法启动,可能是因为文件格式不对,需要修改文件格式

      # vi startup.sh
      :set fileformat=unix
      :wq

  • 访问nacos
    • 在浏览器地址栏输入nacos集群中的一个ip地址,192.168.xxx.xxx:8848/nacos/index.html即可跳转到nacos登录页面,默认用户名和密码都是nacos,登录成功后可看到nacos相关信息。

4. 问题

(1)nacos启动时nacos.log中报xxxIP未加入到集群list中的异常

  • 阿里云的实例有公有ip和私有ip,在nacos的cluster.conf配置文件中配置的是公有ip,但在启动时,nacos会将本机的私有ip自动加入到cluster.conf文件中,导致启动时报xxxIP未加入到集群list中的异常,虽然在页面上可以正常进入到nacos管理页面,但在做dubbo集成时,dubbo的服务调用方会注册不成功。
  • 目前的解决办法

    修改nacos的启动文件,添加本地ip地址配置
    # vi startup.sh
    找到 JVM Configuration 这部分, 在集群参数里增加 -Dnacos.server.ip=xx
    JAVA_OPT=”${JAVA_OPT} -Dnacos.server.ip=xx.xx.xx.xx”