searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

应用多活介绍

2023-06-27 04:51:38
17
0

[AppActive]是阿里云计算产品应用多活AHAS中开源出来的一部分功能,应用多活AHAS主要有三块,第一块流量防护主要是基于阿里本身的[Sentinel]开源项目,与Hystrix类似,用于微服务故障熔断和恢复。第二块故障演练是基于[chaosblade]开源项目,混沌工程,也就是故障注入。最后一块就是多活容灾,这个的能力正是来源于AppActive。目前AppActive开源出来的代码比较简单,也不完善,但可以看出来一些实现思路。

 一、应用双活、多活的原理和实现方案

关于应用双活、多活,首先要了解一些分布式理论如CAP、BASE。可以看看[基于库存的异地双活方案],这是我几年前实现的方案和思路。业务单元化,基于规则的路由/流量调度,业务降级、业务接管与恢复、基于Mysql的双写和主从同步控制缓存、消息、ES等数据同步以达到数据最终一致性等。都是应用双活实现的主要技术点。在云计算时代,结合K8S和容器技术,基础设施更容易管理,多活应该更好做了。

 

二、分析AppActive

首先从github先把代码clone下来。

## 1.了解规则文件

规则文件其实就是一些JSON文件,其中描述了流量的定义、转换和流量转发规则

- idSource.json: 描述如何从 http 流量中提取路由标,比如请示中带有r_id标识或cookie中的用户标识
- idTransformer.json: 描述如何解析路由标
- idUnitMapping.json: 描述路由标和单元的映射关系
- machine.json: 描述当前机器的归属单元
- mysql-product: 描述数据库的属性

## 2.安装组件和推送规则

通过demo代码目录下的sh run-quick.sh进行docker-compose安装和启动应用 

```shell
[docker@ccse-0004 product-center]$ docker-compose ps

    Name                   Command               State                      Ports                   
----------------------------------------------------------------------------------------------------

frontend        sh -c java -jar /app/front ...   Up      0.0.0.0:8885->8885/tcp                     
frontend-unit   sh -c java -jar /app/front ...   Up      0.0.0.0:8886->8886/tcp                     
gateway         nginx -p /etc/nginx -c /et ...   Up      0.0.0.0:80->80/tcp, 0.0.0.0:8090->8090/tcp 
mysql           docker-entrypoint.sh --cha ...   Up      3306/tcp, 33060/tcp, 0.0.0.0:3307->3307/tcp
nacos           bin/docker-startup.sh            Up      0.0.0.0:8848->8848/tcp                     
product         sh -c java -jar /app/produ ...   Up      0.0.0.0:8883->8883/tcp                     
product-unit    sh -c java -jar /app/produ ...   Up      0.0.0.0:8884->8884/tcp                     
storage         sh -c java -jar /app/stora ...   Up      0.0.0.0:8881->8881/tcp                     
storage-unit    sh -c java -jar /app/stora ...   Up      0.0.0.0:8882->8882/tcp  
```

组件作用

- Nacos:服务注册中心,安装的几个微服务使用

- MySql:存储

- gateway:应用网关,执行切流规则。开了两个端口,80给应用使用,8090用于规则推送

- frontend、product、storage则分别是不同的微服务,-unit表示单元化

  安装完成后,访问http://demo.appactive.io/buyProduct?r_id=2000,注意先host文件映射一下域名。

  ![image-20220126012136145](http://img.vinin.me/image-20220126012136145.png)

  推送portal下的baseline.sh:

- 通过 http 通道给 gateway 推送规则,即curl调用nginx 8090,通过lua脚本设置网关流量规则

- 通过 文件 通道给 其他应用 推送规则,即通过cp方式把portal的rule目录下的规则,复制到demo/data对应的应用目录下

## 3.切流

portal下的cut.sh,可以认为portal为管理控制台,cut.sh即为管理控制台给gateway传递切流指令。
执行切流过程:

- 构建新的映射关系规则和禁写规则(手动)
- 将新的映射关系规则推送给gateway
- 将禁写规则推送给其他应用
- 等待数据追平后将新的映射关系规则推送给其他应用

注意,新的映射关系是你想达到的目标状态,而禁写规则是根据目标状态和现状计算出来的差值。当前,这两者都需要你手动设置并更新到 `appactive-portal/rule` 下对应的json文件中去,然后运行 `./cut.sh`

 

cut.sh通过curl调用openresty的set api,把路由规则推送过去。

```shell
gatewayRule="{\"idSource\" : $idSource, \"idTransformer\" : $idTransformer, \"idUnitMapping\" : $idUnitMapping}"
data="{\"key\" : \"459236fc-ed71-4bc4-b46c-69fc60d31f18_test1122\", \"value\" : $gatewayRule}"
echo $data
curl --header "Content-Type: application/json" \
--request POST \
--data "$data" \
127.0.0.1:8090/set
```

通过cp命令把portal rule目录下的文件拷贝到demo应用对应的目录下

```shell
for file in $(ls ../appactive-demo/data/); do
  if [[ "$file" == *"path-address"* ]]; then
    echo "continue"
    continue
  fi
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送中)"
  cp -f ./rule/$forbiddenFile "../appactive-demo/data/$file/forbiddenRule.json"
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送完成"
done

echo "等待数据追平......"
sleep 3s

for file in $(ls ../appactive-demo/data/); do
  if [[ "$file" == *"path-address"* ]]; then
    echo "continue"
    continue
  fi
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送中"
  cp -f ./rule/$idUnitMappingNextFile "../appactive-demo/data/$file/idUnitMapping.json"
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送完成"
done
```

切流完成后,再次刷新,r_id=2000的流量发生了变化 :

![image-20220128002622077](http://img.vinin.me/image-20220128002622077.png)

## 4.gateway实现分析

gateway主要实现规则动态更新,基于Nginx+Lua来实现,openresty是利用Nginx Lua构建的一个web平台,实现通过lua处理http请求。这里的gateway主要由openresty镜像,lua处理脚本和路由规则组成。

/nginx-plugin/etc/conf/sys.conf,定义共享缓存,监听8090端口,处理/get  /set请求

```shell
#openresty共享内存,多nginx worker共享
lua_shared_dict kv_shared_dict 32m;

server {

    listen 8090;

    location /get {
        content_by_lua_file 'conf/lua/kv/kv_get.lua';
    }

    location /set {
        content_by_lua_file 'conf/lua/kv/kv_set.lua';
    }

    location /demo {
        content_by_lua_file 'conf/lua/demo.lua';
    }
}
```

处理set请求的lua脚本在conf/lua/kv/kv_set.lua,处理逻辑类似写数据库同时写缓存。

```lua
--main
local req_method = ngx.var.request_method
if "PUT" == req_method or "POST" == req_method then
    local data = getRuleBody()
    if data then
        local dataDecoded = cjson.decode(data)
        if not dataDecoded then
            kv.print("set value invalid", 400)
        end
        if dataDecoded.key and dataDecoded.value then
            --打开文件,nginx的docker目录/etc/nginx/store
            local f = io.open(kv.storePath .. dataDecoded.key, "w+")
            if f then
                --以key做为文件名,value为作文件内容写入
                local ret = f:write(cjson.encode(dataDecoded.value))
                f:close()
                --写入成功则同时写入缓存
                if ret then
                    local rule_ver = kv.kvShared:get(dataDecoded.key..kv.versionKey)
                    if rule_ver == nil then
                        rule_ver = 1
                    else
                        rule_ver = rule_ver + 1
                    end
                    kv.kvShared:set(dataDecoded.key..kv.versionKey, rule_ver)
                    kv.kvShared:set(dataDecoded.key, cjson.encode(dataDecoded.value))
                    kv.print("success", 200)
                else
                    kv.print("write disk failed", 500)
                end
            else
                kv.print("open file failed", 500)
            end
        else
            kv.print("null key or value not supported", 400)
        end
    end
end
```

## 5.实现流量调度

主要通过nginx配置和lua脚本实现流量控制与调度

nginx配置:

```shell
events {
    use epoll;
    worker_connections 20480;
}

http {

    log_format proxyformat "$status|$upstream_status|$remote_addr|$upstream_addr|$upstream_response_time|$time_local|$request_method|$scheme://$log_host:$server_port$request_uri|$body_bytes_sent|$http_referer|$http_user_agent|$http_x_forwarded_for|$http_accept_language|$connection_requests|$router_rule|$unit_key|$unit|$is_local_unit|$ups|$cell_key|$cell|";
    access_log "logs/access.log" proxyformat;
    #lua相关配置
    lua_package_path "${prefix}/conf/lua/?.lua;;";
    init_by_lua_file 'conf/lua/init_by_lua_file.lua';
    lua_use_default_type off;
    lua_max_pending_timers 32;
    lua_max_running_timers 16;
    #http 8090,用lua脚本处理http请求
    include sys.conf;
    #网关处理
    include apps/*.conf;

}
```

apps/exmaple.conf,通过upstream配置实现应用流量控制

```shell
server {
    listen 80 ;

    server_name demo.appactive.io center.demo.appactive.io unit.demo.appactive.io ;

    include srv.cfg;

    location / {
        set $app "demo_appactive_io@";
        #开始写死了单元类型、规则ID
        set $unit_type test1122;
        set $rule_id 459236fc-ed71-4bc4-b46c-69fc60d31f18;
        set $router_rule ${rule_id}_${unit_type};
        set $unit_key '';
        set $cell_key '';
        set $unit_enable 1;
        #实现proxy配置
        include loc.cfg;
    }

    location /demo {
        set $app "demo_appactive_io@demo";
        set $unit_type test1122;
        set $rule_id 459236fc-ed71-4bc4-b46c-69fc60d31f18;
        set $router_rule ${rule_id}_${unit_type};
        set $unit_key '';
        set $cell_key '';
        set $unit_enable 1;
        include loc.cfg;
    }
}

#中心
upstream demo_appactive_io@_center_default {
    server frontend:8885;
}
#单元
upstream demo_appactive_io@_unit_default {
    server frontend-unit:8886;
}

upstream demo_appactive_io@demo_center_default {
    server 127.0.0.1:8090;
}

upstream demo_appactive_io@demo_unit_default {
    server 127.0.0.1:8090;
}
```

loc.cfg

```shell
#通过脚本计算所属单元
set_by_lua_file $unit "conf/lua/set_user_unit.lua" $router_rule $unit_enable;
if ($unit = "-2") {
    return 500 "wrong route condition";
}
if ($unit = "-1") {
    set $unit $self_unit;
}

set $is_local_unit 1;
if ($unit != $self_unit) {
    set $is_local_unit 0;
}

#计算upstream name
set $ups "${app}_${unit}";

set $cell "default";
set $ups "${ups}_${cell}";

# attention no _ in key
proxy_set_header "unit-type" $unit_type;
proxy_set_header "unit" $unit;
proxy_set_header "unit-key" $unit_key;
proxy_set_header "host" $host;

proxy_pass http://$ups;
```

set_user_unit.lua

```lua
local kv = require("kv.kv_util")
local ruleChecker = require("util.rule_checker")
local unitFilter = require("util.unit_filter")

local function doMain()
    -- rule_id
    local ruleKey = ngx.arg[1]
    -- unit enable?
    local unitEnabled = ngx.arg[2]
    -- 获取规则原始内容
    local ruleRaw = kv.get(ruleKey)
    -- 规则版本
    local ruleRawVersion = kv.get(ruleKey ..kv.versionKey)
    -- 规则转换检查 
    local ruleParsed = ruleChecker.doCheckRule(ruleRawVersion, ruleKey, ruleRaw)
    -- 计算出单元编号
    local unit = unitFilter.getUnitForRequest(ruleParsed, unitEnabled == '1')
    return unit

end

-- main
local ok, res = pcall(doMain)
if not ok then
    ngx.log(ngx.ERR, "[unit] calc error "..res);
    return -1
else
    ngx.log(ngx.INFO, "[unit] calc "..res);
    return res
end

```

 

三、总结

目前开源的比较简陋,感觉没有达到生产级可用,需要根据自己的产品规划理解后,再进行开发,网关的核心就是nginx + lua。服务层主要是基于Dubbo,数据层是Mysql,这里使用有局限性,后面再分析。

0条评论
0 / 1000
chuoo
13文章数
0粉丝数
chuoo
13 文章 | 0 粉丝
原创

应用多活介绍

2023-06-27 04:51:38
17
0

[AppActive]是阿里云计算产品应用多活AHAS中开源出来的一部分功能,应用多活AHAS主要有三块,第一块流量防护主要是基于阿里本身的[Sentinel]开源项目,与Hystrix类似,用于微服务故障熔断和恢复。第二块故障演练是基于[chaosblade]开源项目,混沌工程,也就是故障注入。最后一块就是多活容灾,这个的能力正是来源于AppActive。目前AppActive开源出来的代码比较简单,也不完善,但可以看出来一些实现思路。

 一、应用双活、多活的原理和实现方案

关于应用双活、多活,首先要了解一些分布式理论如CAP、BASE。可以看看[基于库存的异地双活方案],这是我几年前实现的方案和思路。业务单元化,基于规则的路由/流量调度,业务降级、业务接管与恢复、基于Mysql的双写和主从同步控制缓存、消息、ES等数据同步以达到数据最终一致性等。都是应用双活实现的主要技术点。在云计算时代,结合K8S和容器技术,基础设施更容易管理,多活应该更好做了。

 

二、分析AppActive

首先从github先把代码clone下来。

## 1.了解规则文件

规则文件其实就是一些JSON文件,其中描述了流量的定义、转换和流量转发规则

- idSource.json: 描述如何从 http 流量中提取路由标,比如请示中带有r_id标识或cookie中的用户标识
- idTransformer.json: 描述如何解析路由标
- idUnitMapping.json: 描述路由标和单元的映射关系
- machine.json: 描述当前机器的归属单元
- mysql-product: 描述数据库的属性

## 2.安装组件和推送规则

通过demo代码目录下的sh run-quick.sh进行docker-compose安装和启动应用 

```shell
[docker@ccse-0004 product-center]$ docker-compose ps

    Name                   Command               State                      Ports                   
----------------------------------------------------------------------------------------------------

frontend        sh -c java -jar /app/front ...   Up      0.0.0.0:8885->8885/tcp                     
frontend-unit   sh -c java -jar /app/front ...   Up      0.0.0.0:8886->8886/tcp                     
gateway         nginx -p /etc/nginx -c /et ...   Up      0.0.0.0:80->80/tcp, 0.0.0.0:8090->8090/tcp 
mysql           docker-entrypoint.sh --cha ...   Up      3306/tcp, 33060/tcp, 0.0.0.0:3307->3307/tcp
nacos           bin/docker-startup.sh            Up      0.0.0.0:8848->8848/tcp                     
product         sh -c java -jar /app/produ ...   Up      0.0.0.0:8883->8883/tcp                     
product-unit    sh -c java -jar /app/produ ...   Up      0.0.0.0:8884->8884/tcp                     
storage         sh -c java -jar /app/stora ...   Up      0.0.0.0:8881->8881/tcp                     
storage-unit    sh -c java -jar /app/stora ...   Up      0.0.0.0:8882->8882/tcp  
```

组件作用

- Nacos:服务注册中心,安装的几个微服务使用

- MySql:存储

- gateway:应用网关,执行切流规则。开了两个端口,80给应用使用,8090用于规则推送

- frontend、product、storage则分别是不同的微服务,-unit表示单元化

  安装完成后,访问http://demo.appactive.io/buyProduct?r_id=2000,注意先host文件映射一下域名。

  ![image-20220126012136145](http://img.vinin.me/image-20220126012136145.png)

  推送portal下的baseline.sh:

- 通过 http 通道给 gateway 推送规则,即curl调用nginx 8090,通过lua脚本设置网关流量规则

- 通过 文件 通道给 其他应用 推送规则,即通过cp方式把portal的rule目录下的规则,复制到demo/data对应的应用目录下

## 3.切流

portal下的cut.sh,可以认为portal为管理控制台,cut.sh即为管理控制台给gateway传递切流指令。
执行切流过程:

- 构建新的映射关系规则和禁写规则(手动)
- 将新的映射关系规则推送给gateway
- 将禁写规则推送给其他应用
- 等待数据追平后将新的映射关系规则推送给其他应用

注意,新的映射关系是你想达到的目标状态,而禁写规则是根据目标状态和现状计算出来的差值。当前,这两者都需要你手动设置并更新到 `appactive-portal/rule` 下对应的json文件中去,然后运行 `./cut.sh`

 

cut.sh通过curl调用openresty的set api,把路由规则推送过去。

```shell
gatewayRule="{\"idSource\" : $idSource, \"idTransformer\" : $idTransformer, \"idUnitMapping\" : $idUnitMapping}"
data="{\"key\" : \"459236fc-ed71-4bc4-b46c-69fc60d31f18_test1122\", \"value\" : $gatewayRule}"
echo $data
curl --header "Content-Type: application/json" \
--request POST \
--data "$data" \
127.0.0.1:8090/set
```

通过cp命令把portal rule目录下的文件拷贝到demo应用对应的目录下

```shell
for file in $(ls ../appactive-demo/data/); do
  if [[ "$file" == *"path-address"* ]]; then
    echo "continue"
    continue
  fi
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送中)"
  cp -f ./rule/$forbiddenFile "../appactive-demo/data/$file/forbiddenRule.json"
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送完成"
done

echo "等待数据追平......"
sleep 3s

for file in $(ls ../appactive-demo/data/); do
  if [[ "$file" == *"path-address"* ]]; then
    echo "continue"
    continue
  fi
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送中"
  cp -f ./rule/$idUnitMappingNextFile "../appactive-demo/data/$file/idUnitMapping.json"
  echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送完成"
done
```

切流完成后,再次刷新,r_id=2000的流量发生了变化 :

![image-20220128002622077](http://img.vinin.me/image-20220128002622077.png)

## 4.gateway实现分析

gateway主要实现规则动态更新,基于Nginx+Lua来实现,openresty是利用Nginx Lua构建的一个web平台,实现通过lua处理http请求。这里的gateway主要由openresty镜像,lua处理脚本和路由规则组成。

/nginx-plugin/etc/conf/sys.conf,定义共享缓存,监听8090端口,处理/get  /set请求

```shell
#openresty共享内存,多nginx worker共享
lua_shared_dict kv_shared_dict 32m;

server {

    listen 8090;

    location /get {
        content_by_lua_file 'conf/lua/kv/kv_get.lua';
    }

    location /set {
        content_by_lua_file 'conf/lua/kv/kv_set.lua';
    }

    location /demo {
        content_by_lua_file 'conf/lua/demo.lua';
    }
}
```

处理set请求的lua脚本在conf/lua/kv/kv_set.lua,处理逻辑类似写数据库同时写缓存。

```lua
--main
local req_method = ngx.var.request_method
if "PUT" == req_method or "POST" == req_method then
    local data = getRuleBody()
    if data then
        local dataDecoded = cjson.decode(data)
        if not dataDecoded then
            kv.print("set value invalid", 400)
        end
        if dataDecoded.key and dataDecoded.value then
            --打开文件,nginx的docker目录/etc/nginx/store
            local f = io.open(kv.storePath .. dataDecoded.key, "w+")
            if f then
                --以key做为文件名,value为作文件内容写入
                local ret = f:write(cjson.encode(dataDecoded.value))
                f:close()
                --写入成功则同时写入缓存
                if ret then
                    local rule_ver = kv.kvShared:get(dataDecoded.key..kv.versionKey)
                    if rule_ver == nil then
                        rule_ver = 1
                    else
                        rule_ver = rule_ver + 1
                    end
                    kv.kvShared:set(dataDecoded.key..kv.versionKey, rule_ver)
                    kv.kvShared:set(dataDecoded.key, cjson.encode(dataDecoded.value))
                    kv.print("success", 200)
                else
                    kv.print("write disk failed", 500)
                end
            else
                kv.print("open file failed", 500)
            end
        else
            kv.print("null key or value not supported", 400)
        end
    end
end
```

## 5.实现流量调度

主要通过nginx配置和lua脚本实现流量控制与调度

nginx配置:

```shell
events {
    use epoll;
    worker_connections 20480;
}

http {

    log_format proxyformat "$status|$upstream_status|$remote_addr|$upstream_addr|$upstream_response_time|$time_local|$request_method|$scheme://$log_host:$server_port$request_uri|$body_bytes_sent|$http_referer|$http_user_agent|$http_x_forwarded_for|$http_accept_language|$connection_requests|$router_rule|$unit_key|$unit|$is_local_unit|$ups|$cell_key|$cell|";
    access_log "logs/access.log" proxyformat;
    #lua相关配置
    lua_package_path "${prefix}/conf/lua/?.lua;;";
    init_by_lua_file 'conf/lua/init_by_lua_file.lua';
    lua_use_default_type off;
    lua_max_pending_timers 32;
    lua_max_running_timers 16;
    #http 8090,用lua脚本处理http请求
    include sys.conf;
    #网关处理
    include apps/*.conf;

}
```

apps/exmaple.conf,通过upstream配置实现应用流量控制

```shell
server {
    listen 80 ;

    server_name demo.appactive.io center.demo.appactive.io unit.demo.appactive.io ;

    include srv.cfg;

    location / {
        set $app "demo_appactive_io@";
        #开始写死了单元类型、规则ID
        set $unit_type test1122;
        set $rule_id 459236fc-ed71-4bc4-b46c-69fc60d31f18;
        set $router_rule ${rule_id}_${unit_type};
        set $unit_key '';
        set $cell_key '';
        set $unit_enable 1;
        #实现proxy配置
        include loc.cfg;
    }

    location /demo {
        set $app "demo_appactive_io@demo";
        set $unit_type test1122;
        set $rule_id 459236fc-ed71-4bc4-b46c-69fc60d31f18;
        set $router_rule ${rule_id}_${unit_type};
        set $unit_key '';
        set $cell_key '';
        set $unit_enable 1;
        include loc.cfg;
    }
}

#中心
upstream demo_appactive_io@_center_default {
    server frontend:8885;
}
#单元
upstream demo_appactive_io@_unit_default {
    server frontend-unit:8886;
}

upstream demo_appactive_io@demo_center_default {
    server 127.0.0.1:8090;
}

upstream demo_appactive_io@demo_unit_default {
    server 127.0.0.1:8090;
}
```

loc.cfg

```shell
#通过脚本计算所属单元
set_by_lua_file $unit "conf/lua/set_user_unit.lua" $router_rule $unit_enable;
if ($unit = "-2") {
    return 500 "wrong route condition";
}
if ($unit = "-1") {
    set $unit $self_unit;
}

set $is_local_unit 1;
if ($unit != $self_unit) {
    set $is_local_unit 0;
}

#计算upstream name
set $ups "${app}_${unit}";

set $cell "default";
set $ups "${ups}_${cell}";

# attention no _ in key
proxy_set_header "unit-type" $unit_type;
proxy_set_header "unit" $unit;
proxy_set_header "unit-key" $unit_key;
proxy_set_header "host" $host;

proxy_pass http://$ups;
```

set_user_unit.lua

```lua
local kv = require("kv.kv_util")
local ruleChecker = require("util.rule_checker")
local unitFilter = require("util.unit_filter")

local function doMain()
    -- rule_id
    local ruleKey = ngx.arg[1]
    -- unit enable?
    local unitEnabled = ngx.arg[2]
    -- 获取规则原始内容
    local ruleRaw = kv.get(ruleKey)
    -- 规则版本
    local ruleRawVersion = kv.get(ruleKey ..kv.versionKey)
    -- 规则转换检查 
    local ruleParsed = ruleChecker.doCheckRule(ruleRawVersion, ruleKey, ruleRaw)
    -- 计算出单元编号
    local unit = unitFilter.getUnitForRequest(ruleParsed, unitEnabled == '1')
    return unit

end

-- main
local ok, res = pcall(doMain)
if not ok then
    ngx.log(ngx.ERR, "[unit] calc error "..res);
    return -1
else
    ngx.log(ngx.INFO, "[unit] calc "..res);
    return res
end

```

 

三、总结

目前开源的比较简陋,感觉没有达到生产级可用,需要根据自己的产品规划理解后,再进行开发,网关的核心就是nginx + lua。服务层主要是基于Dubbo,数据层是Mysql,这里使用有局限性,后面再分析。

文章来自个人专栏
容器
13 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0