当前位置: 首页 > news >正文

昆明云南微网站建设小困网络科技泰安有限公司

昆明云南微网站建设,小困网络科技泰安有限公司,成都网站建设 工资,自己的网站做优化怎么设置缓存文章目录服务发现集成consul负载均衡负载均衡算法实现配置中心nacos服务发现 我们之前的架构是通过ipport来调用的python的API#xff0c;这样做的弊端是 如果新加一个服务#xff0c;就要到某个服务改web#xff08;go#xff09;层的调用代码#xff0c;配置IP/Port并发… 文章目录服务发现集成consul负载均衡负载均衡算法实现配置中心nacos服务发现 我们之前的架构是通过ipport来调用的python的API这样做的弊端是 如果新加一个服务就要到某个服务改webgo层的调用代码配置IP/Port并发过高需要加机器时其他服务也要重新部署否则找不到新加的node 如何解决这些问题呢我们一般将多个服务进行注册 所有服务注册到注册中心获取其他服务时通过这里拉取配置信息grpc也就是通过这配置的传输序列化信息其实就是将IPPort放到这统一管理注册中心必须有健康检查的功能所以不能使用Redis服务网关是对接用户的屏障但也要能和注册中心交流才能路由到具体服务 技术选型 Paxos和Raft算法可以私下偷偷了解一下 consul的安装和配置 能用docker就不搞太多麻烦的步骤 # 8500和8600端口常用 docker run -d -p 8500:8500 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client0.0.0.0 docker container update --restartalways containerName/ID访问consulip:8500/ui/dc1/services可以看出consul是支持KV存储的但没有Redis强 consul提供DNS功能域名创建/解析就是可以自定义域名我们目前写的服务都可以通过ipport方式注册和拉取但如果有其他服务不想通过consul但希望能拉取各服务这就需要consul能将自己和各服务域名化方便外面的服务调用 可以用dig命令查看consul的DNS功能dig ip -p 8600 cosul1.service.consul SRV就是在服务名后跟上service.consul类似www.baidu.com yum install bind-utils consul的API接口 注册服务使用PUT请求路径也指定了然后就是传递指定的参数了json格式 类似的可以删除服务、配置健康检查写个测试先直接使用requests请求consul接口import requestsheaders {contentType:application/json }def register(name, id, address, port):url http://192.168.109.129:8500/v1/agent/service/registerprint(fhttp://{address}:{port}/health)rsp requests.put(url, headersheaders, json{Name:name, # 服务名称 Specifies the logical name of the service.ID:id, # 服务ID Specifies a unique ID for this serviceTags:[mxshop, Roy, veritas, web],Address:address, # 服务IPPort:port, # 服务Port})if rsp.status_code 200:print(注册成功)else:print(f注册失败{rsp.status_code})# 启动service if __name__ __main__:register(mxshop-python, mxshop-py, 192.168.109.1, 50051) # consul在Linux虚拟机109.129服务在Windows用VMnet8的地址删除服务# 删除服务有id即可 def deregister(id):url fhttp://192.168.109.129:8500/v1/agent/service/deregister/{id}rsp requests.put(url, headersheaders)if rsp.status_code 200:print(注销成功)else:print(f注销失败{rsp.status_code})过滤服务其实就是服务发现输入你想找的服务名这里还没有用DNS域名来搜索# 列出指定的服务 def filter_service(name):url http://192.168.109.129:8500/v1/agent/servicesparams {filter: fService {name}}rsp requests.get(url, paramsparams).json()for key, value in rsp.items():print(key)健康检查先配置针对HTTP的比较简单grpc的会复杂一些稍后整~ # 配置Check即可 def register(name, id, address, port):url http://192.168.109.129:8500/v1/agent/service/registerprint(fhttp://{address}:{port}/health)rsp requests.put(url, headersheaders, json{Name:name, # 服务名称 Specifies the logical name of the service.ID:id, # 服务ID Specifies a unique ID for this serviceTags:[mxshop, Roy, veritas, web],Address:address, # 服务IPPort:port, # 服务PortCheck: {# GRPC:f{address}:{port},# GRPCUseTLS: False,HTTP:fhttp://{address}:{port}/health, # 需要在go-web写这个路由的逻辑handler直接在main-initialize不下到router-api了Timeout: 5s,Interval: 5s,DeregisterCriticalServiceAfter: 15s}})if rsp.status_code 200:print(注册成功)else:print(f注册失败{rsp.status_code})if __name__ __main__:# 注册go-webregister(mxshop-go, mxshop-web, 192.168.109.1, 8021)# deregister(mshop-web)# 然后给它配上健康检查filter_service(mxshop-go) # 传name// go-web package initializeimport (github.com/gin-gonic/ginmxshop/user-web/middlewaresrouter2 mxshop/user-web/routernet/http )func Routers() *gin.Engine {Router : gin.Default() // 全局 gin-context// 定义一个临时的路由测试consul注册Router.GET(/health, func(context *gin.Context) {context.JSON(http.StatusOK, gin.H{code: http.StatusOK,success: true,})})Router.Use(middlewares.Cors()) // 配置跨域// 统一一下路由传入group前缀用户服务都用这个ApiGroup : Router.Group(/u/v1) // 版本号加个u方便后续测试router2.InitUserRouter(ApiGroup)return Router } // 确保consul的容器能和服务ping通配置GRPC的健康检查假设我们要给python层的user-srv配置 参考官方文档我们需要使用proto文件生成相关的代码下面是生成好的代码主要是实现proto文件中的Check和Watch方法 当然是注册到当前的服务server用到它这里的proto-pb2_grpc和注册我们之前写的handler一样UserServicer这里要注册人家的handlerHealthServicer代码补充# server.py # 注意这个路径可能要改改 from common.grpc_health.v1 import health_pb2_grpc, health_pb2 from common.grpc_health.v1 import health# 注册健康检查 health_pb2_grpc.add_HealthServicer_to_server(health.HealthServicer(), server)consul中设置端口号把HTTP换成这两行GRPC:f{address}:{port}, GRPCUseTLS: False, // 不检查证书啥的要把服务启动保证外部能ping通测试一下 上面是容器化的consul搞个consul服务也可以直接在python端用第三方实现服务注册还是consul 安装pip install -i https://pypi.douban.com/simple python-consul2官方文档就在GitHub搜吧这个东西对GRPC支持的不完善就是比较省事建议像上面自己写服务注册的代码import consulc consul.Consul(host192.168.109.129)address 192.168.109.1 port 50051 check{GRPC:f{address}:{port},GRPCUseTLS: False,Timeout: 5s,Interval: 5s,DeregisterCriticalServiceAfter: 15s }# rsp c.agent.service.register(nameuser-srv, service_iduser-srv2, # addressaddress, portport, tags[mxshop],checkcheck) rsp c.agent.services() for key, val in rsp.items():rsp c.agent.service.deregister(key) # print(rsp)在go语言层面使用consul类似在python写代码那样 包括服务注册检查和发现package mainimport (fmtgithub.com/hashicorp/consul/api )func Register(address string, port int, name string, tags []string, id string) error {cfg : api.DefaultConfig()cfg.Address 192.168.1.103:8500client, err : api.NewClient(cfg)if err ! nil {panic(err)}//生成对应的检查对象基于HTTP直接实例化check : api.AgentServiceCheck{HTTP: http://192.168.109.129:8021/health,Timeout: 5s,Interval: 5s,DeregisterCriticalServiceAfter: 10s,}//生成注册对象通过new方法生成对象有啥区别呢忘了registration : new(api.AgentServiceRegistration)registration.Name nameregistration.ID idregistration.Port portregistration.Tags tagsregistration.Address addressregistration.Check checkerr client.Agent().ServiceRegister(registration)client.Agent().ServiceDeregister()if err ! nil {panic(err)}return nil }func AllServices() {cfg : api.DefaultConfig()cfg.Address 192.168.109.129:8500client, err : api.NewClient(cfg)if err ! nil {panic(err)}data, err : client.Agent().Services()if err ! nil {panic(err)}for key, _ : range data {fmt.Println(key)} }// 服务发现 func FilterSerivice() {cfg : api.DefaultConfig()cfg.Address 192.168.109.129:8500client, err : api.NewClient(cfg)if err ! nil {panic(err)}data, err : client.Agent().ServicesWithFilter(Service user-web) // 写死了sorryif err ! nil {panic(err)}for key, _ : range data {fmt.Println(key)} }func main() {//_ Register(192.168.109.1, 8021, user-web, []string{mxshop, Roy}, user-web)//AllServices()//FilterSerivice()fmt.Println(fmt.Sprintf(Service %s, user-srv)) }python层定义了逻辑提供给go层进行rpc所以python层需要支持grpc的checkgo层只需要支持HTTP的checkpython层也可以进行HTTP的check只要是运行在ipport上的服务虽然不能通过HTTP直接调用没路由问题只定义了proto文件和handler注册go怎么找到python API的grpc.Dial() 集成consul 上面是测试consul各自进行了服务注册和模拟发现现在需要集成到我们的项目让python层和go-web都能用上在python服务注册common/register定义抽象类并实现服务注册、删除、获取所有、发现 abc.abstractmethod之前引入了grpc的health-check直接使用for go-web如果希望HTTP-DNS形式的调用呢 在settings中加入consul服务器的配置这个只能IPPort总得有个固定的手动操作的配置在server.py中调用启动from common.grpc_health.v1 import health_pb2_grpc, health_pb2 from common.grpc_health.v1 import health from common.register import consul from settings import settings# 注册健康检查 health_pb2_grpc.add_HealthServicer_to_server(health.HealthServicer(), server)# consul服务注册 logger.info(f服务注册开始) register consul.ConsulRegister(settings.CONSUL_HOST, settings.CONSUL_PORT) if not register.register(namesettings.SERVICE_NAME, idsettings.SERVICE_NAME,addressargs.ip, portargs.port, tagssettings.SERVICE_TAGS, checkNone):logger.info(f服务注册失败)sys.exit(0) logger.info(f服务注册成功)在go进行服务发现但因为逻辑比较繁琐放在initialize/srv_conn.go然后做一个全局变量放在global这个脚本只需要赋值这个全局变量就好记得在config加上配置IPPortpackage initializeimport (fmtgithub.com/hashicorp/consul/api_ github.com/mbobakov/grpc-consul-resolver // Its importantgo.uber.org/zapgoogle.golang.org/grpcmxshop/user-web/globalmxshop/user-web/proto )func InitSrvConn() {consulInfo : global.ServerConfig.ConsulInfouserConn, err : grpc.Dial(fmt.Sprintf(consul://%s:%d/%s?wait14s, consulInfo.Host, consulInfo.Port, global.ServerConfig.UserSrvInfo.Name),grpc.WithInsecure(),grpc.WithDefaultServiceConfig({loadBalancingPolicy: round_robin}),)if err ! nil {zap.S().Fatal([InitSrvConn] 连接 【用户服务失败】)}userSrvClient : proto.NewUserClient(userConn)global.UserSrvClient userSrvClient }func InitSrvConn2() {// 从注册中心获取到python层用户服务的信息cfg : api.DefaultConfig()consulInfo : global.ServerConfig.ConsulInfocfg.Address fmt.Sprintf(%s:%d, consulInfo.Host, consulInfo.Port)userSrvHost : userSrvPort : 0client, err : api.NewClient(cfg)if err ! nil {panic(err)}// 也可以使用转义符 \%s\用 代替 data, err : client.Agent().ServicesWithFilter(fmt.Sprintf(Service %s, global.ServerConfig.UserSrvInfo.Name))//data, err : client.Agent().ServicesWithFilter(fmt.Sprintf(Service %s, global.ServerConfig.UserSrvInfo.Name))if err ! nil {panic(err)}for _, value : range data {userSrvHost value.AddressuserSrvPort value.Portbreak}if userSrvHost {zap.S().Fatal([InitSrvConn] 连接 【用户服务失败】)return}// 拨号连接用户grpc服务器 跨域的问题 - 后端解决 也可以前端来解决userConn, err : grpc.Dial(fmt.Sprintf(%s:%d, userSrvHost, userSrvPort), grpc.WithInsecure())if err ! nil {zap.S().Errorw([GetUserList] 连接 【用户服务失败】,msg, err.Error(),)}//1. 后续的用户服务下线了 2. 改端口了 3. 改ip了 怎么办 负载均衡来做//2. 已经事先创立好了连接这样后续就不用进行再次tcp的三次握手//3. 一个连接多个groutine共用要性能提升 - 可以使用连接池不是很理解一个用户难道能同时多个操作userSrvClient : proto.NewUserClient(userConn)global.UserSrvClient userSrvClient } // 记得在main中调用grpc.Dial()也放到这得到python层的服务信息IPPort后直接建立连接两条主要流程贯穿main-init-config-yml (viper支持)main-init-router-api/handlerglobal为了方便测试可以先注释掉验证码我们是基于内存做的所以只是设置false不管用除非改成基于Redis的 负载均衡 先解决一个问题动态获取随机可用端口号def get_free_tcp_port():tcp socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp.bind((, 0))_, port tcp.getsockname()tcp.close()return port这样我们就可以在service运行的时候不必指定端口号# 还是在server.py改 def serve():parser argparse.ArgumentParser()parser.add_argument(--ip,nargs?,typestr,default192.168.0.103,helpbinding ip)parser.add_argument(--port,nargs?,typeint,default0,helpthe listening port)args parser.parse_args()if args.port 0:port get_free_tcp_port()else:port args.port # 服务注册的时候也要改成port类似的go-web那边也要改我们放在utils下package utilsimport (net )func GetFreePort() (int, error) {addr, err : net.ResolveTCPAddr(tcp, localhost:0)if err ! nil {return 0, err}l, err : net.ListenTCP(tcp, addr)if err ! nil {return 0, err}defer l.Close()return l.Addr().(*net.TCPAddr).Port, nil }go需要python的服务port动态了就必须靠consul的服务发现任何层的任何服务都注册到consul通过服务名称即可发现什么是负载均衡 这个问题说起来挺复杂的比如我们可以将架构做成如图所示 因为一个网关也不能扛得住还得是集群通过NGINX做负载均衡如今是业界公认可以参考我的笔记 还有一种方法就是进程内的load balance将负载均衡的算法以SDK的方式实现在客户端进程内 最大的区别就是没有负载均衡器比如上图的NGINX这样更稳一点如图在用户-webconsumer实现就事先让web的机器使用协程将所有的能提供用户-srv的机器连上然后用特定的算法在本地提供web服务的这个机器自身安排如何使用服务 不太好的就是不同的语言要写不同的SDK因为依赖web进程的协程但grpc还是用这种主流方法 还有一种改进的方法就是将load balance算法和服务发现连接服务在web机器上以单独的进程实现 这个方法避免了写不同的语言的SDK当然也有缺陷增加维护成本还要单独监控 负载均衡算法 最简单的就是轮循法挨着拿活还有一致性hash可以了解 实现 还是借助grpc实现负载均衡根据官方文档的描述它提供了综合策略 均衡算法可以是第三方也可以自己搞最好不要使用第三方的服务本身也没有继承服务注册中心但是留了接口可以指定从哪进行服务发现而且有人写了包用于集成grpc和consul实现了负载均衡测试一下关键是把consul的URL配置写正确 当然用到grpc必须把那份proto拿进来这次拨号是拨consul服务的测试代码python层启动两个server注意这里需要import uuid得到service_id不然consul还是只有一个user_srv并使用partial()将on_exit()包装成其他函数package mainimport (contextfmtlogmxshop/user-web/grpclb_test/proto// 导入却没有使用其实底层是在import时在init中注册到了grpc_ github.com/mbobakov/grpc-consul-resolver // Its importantgoogle.golang.org/grpc )func main() {conn, err : grpc.Dial(// 这里的名字要跟consul中注册的一样tag也必须一样consul://192.168.109.129:8500/user-srv?wait14stagsrv,grpc.WithInsecure(),grpc.WithDefaultServiceConfig({loadBalancingPolicy: round_robin}), // 轮询)if err ! nil {log.Fatal(err)}defer conn.Close()// 为了在一次进程中连续请求查看负载均衡的效果如果采用多次启动每次轮循的起点都是0没效果for i : 0; i 10; i {userSrvClient : proto.NewUserClient(conn)rsp, err : userSrvClient.GetUserList(context.Background(), proto.PageInfo{Pn: 1,PSize: 2,})if err ! nil {panic(err)}for index, data : range rsp.Data {fmt.Println(index, data)}}}再温习一下grpc这里单独测试使用的是Dial(cfg)proto.NewUserClient()项目里集成使用api.NewUserClient()cfg的形式包含了拨号 集成到项目更新InitSrvConn()使用测试形式手动拨号func InitSrvConn() {consulInfo : global.ServerConfig.ConsulInfouserConn, err : grpc.Dial(fmt.Sprintf(consul://%s:%d/%s?wait14s, consulInfo.Host, consulInfo.Port, global.ServerConfig.UserSrvInfo.Name),grpc.WithInsecure(),grpc.WithDefaultServiceConfig({loadBalancingPolicy: round_robin}),)if err ! nil {zap.S().Fatal([InitSrvConn] 连接 【用户服务失败】)}userSrvClient : proto.NewUserClient(userConn)global.UserSrvClient userSrvClient }配置中心 目前我们的配置是基于本地的配置文件如果配置文件修改了需要重启服务这很不合适所以引入了viper可以自动监听配置文件的修改但还是会有一些问题 总结起来就是不改还行改了必死肯定会改必死所以我们使用配置中心类似服务注册中心支持的功能 实例可以拉取配置权限控制配置回滚环境隔离搭建集群 技术选型流行的框架都是Java支持的但是也有多语言的比如Apollo或者nacos nacos 学会一个其他就会了nacos是阿里做的安装还是使用docker如果懂点Java也可以手动安装执行命令docker run --name nacos-standalone -e MODEstandalone -e JVM_XMS512m -e JVM_XMX512m -e JVM_XMN256m -p 8848:8848 -d nacos/nacos-server:latest # 访问192.168.109.129:8848/nacosnacos/nacos # 注意上面那个mode要大写不然可能登录之后尝试使用有一些概念 命名空间可以隔离配置集一般一个微服务一个命名空间组就是命名空间内区分测试配置和生产环境配置的dataid一个配置集其实就是一个配置文件 集成到python层 直接安装pip install nacos-sdk-python因为同步到pypi了 把settings中的字段加上去使用json很方便所以我们在nacos尽量使用jsonuser-srv.json分group dev/pro {name: user-srv,tags: [Roy, mxshop, python],mysql: {db: mxshop_user_srv,host: 192.168.109.129,port: 3306,user: root,psd: root},consul: {host: 192.168.109.129,port: 8500} }settings就根据namespacegroupdataid定位并拉取配置用data赋值 import jsonimport nacos from playhouse.pool import PooledMySQLDatabase from playhouse.shortcuts import ReconnectMixin from loguru import logger# 使用peewee的连接池 使用ReconnectMixin来防止出现连接断开查询失败 class ReconnectMysqlDatabase(ReconnectMixin, PooledMySQLDatabase):passNACOS {Host: 192.168.109.129,Port: 8848,NameSpace: c1872978-d51c-4188-a497-4e0cd20b97d5, # 新建user namespaceUser: nacos,Password: nacos,DataId: user-srv.json,Group: dev }# 这个client就是NacosClient的实例包含了我们需要的方法 client nacos.NacosClient(f{NACOS[Host]}:{NACOS[Port]}, namespaceNACOS[NameSpace],usernameNACOS[User],passwordNACOS[Password])# get config data client.get_config(NACOS[DataId], NACOS[Group]) data json.loads(data) logger.info(data)# 这里 def update_cfg(args):print(配置产生变化)print(args)# consul的配置 CONSUL_HOST data[consul][host] CONSUL_PORT data[consul][port]# 服务相关的配置 SERVICE_NAME data[name] SERVICE_TAGS data[tags]DB ReconnectMysqlDatabase(data[mysql][db], hostdata[mysql][host], portdata[mysql][port],userdata[mysql][user], passworddata[mysql][password])这里的update_cfg()函数是添加watcher时使用的如果监听到config变化会执行这个函数官网有介绍add_config_watchers(data_id, group, cb_list) 这里不能直接在settings添加会报错 # serve.py if __name__ __main__:logging.basicConfig()settings.client.add_config_watcher(settings.NACOS[DataId], settings.NACOS[Group], settings.update_cfg)serve()集成到go-web层先去nacos发布这里搞成yaml也行哈基本上跟着官网的代码就很可以 go本身支持jsonjson-tag所以我们就把yaml转换一下之前的配置字段都放在config/config.go而且变成了struct方便操作现在拉取json字符串后想要解析成对应的struct当然要在struct中加上json-tag之前的mapstructure:name是针对viper的然后定义NacosConfig的structviper只需要用本地配置文件实例化它就可以type NacosConfig struct {Host string mapstructure:hostPort uint64 mapstructure:portNamespace string mapstructure:namespaceUser string mapstructure:userPassword string mapstructure:passwordDataId string mapstructure:dataidGroup string mapstructure:group }// config-debug.yaml 本地配置文件 host: 192.168.0.104 port: 8848 namespace: c1872978-d51c-4188-a497-4e0cd20b97d5 user: nacos password: nacos dataid: user-web.json group: dev更新init/config.go前面的ReadConfig目的是获取Nacos的连接信息获取配置contentimport (github.com/nacos-group/nacos-sdk-go/clientsgithub.com/nacos-group/nacos-sdk-go/common/constantgithub.com/nacos-group/nacos-sdk-go/vo )func InitConfig() {debug : GetEnvInfo(IS_DEBUG)configFilePrefix : configconfigFileName : fmt.Sprintf(user-web/%s-pro.yaml, configFilePrefix)if debug {configFileName fmt.Sprintf(user-web/%s-debug.yaml, configFilePrefix)}v : viper.New()// 文件的路径如何设置v.SetConfigFile(configFileName)if err : v.ReadInConfig(); err ! nil {panic(err)}// 这个对象如何在其他文件中使用 - 全局变量if err : v.Unmarshal(global.NacosConfig); err ! nil { // 解析nacos的连接信息赋值给NacosConfigpanic(err)}zap.S().Infof(配置信息存放在: v, global.NacosConfig)// 从nacos中读取配置信息sc : []constant.ServerConfig{{IpAddr: global.NacosConfig.Host,Port: global.NacosConfig.Port,},}cc : constant.ClientConfig{NamespaceId: global.NacosConfig.Namespace, // 如果需要支持多namespace我们可以场景多个client,它们有不同的NamespaceIdTimeoutMs: 5000,NotLoadCacheAtStart: true,LogDir: tmp/nacos/log,CacheDir: tmp/nacos/cache,RotateTime: 1h,MaxAge: 3,LogLevel: debug,}configClient, err : clients.CreateConfigClient(map[string]interface{}{serverConfigs: sc,clientConfig: cc,})if err ! nil {panic(err)}content, err : configClient.GetConfig(vo.ConfigParam{DataId: global.NacosConfig.DataId,Group: global.NacosConfig.Group})if err ! nil {panic(err)}//fmt.Println(content) //字符串 - yaml// 想要将一个json字符串转换成struct需要到struct设置json-tag// 再用拉取到的content赋值项目的配置信息err json.Unmarshal([]byte(content), global.ServerConfig)if err ! nil {zap.S().Fatalf(读取nacos配置失败 %s, err.Error())}fmt.Println(global.ServerConfig) }记得在mx-shop下新建tmp/nacos/log和tmp/nacos/cache目录存放缓存也便于回滚 接下来就要继续完善其他服务了先开始Java-Web
http://www.ho-use.cn/article/10823086.html

相关文章:

  • 可信网站 如何验证小程序店铺
  • 如何查看网站备案信息网站验证码体验
  • 网站建设收税简单一点的网站建设
  • phpcms二级栏目文章列表调用网站最新文章的方法天猫官方网站首页
  • 商城做网站好还是淘宝合肥网站开发需要多
  • 建站模板建网站个人个性网页界面设计
  • 绍兴网站制作公司ipv6网站建设东莞
  • 一般网站建设需要哪些东西wordpress会员小图标
  • 荆州市做网站的wordpress搬家出问题
  • 呼市网站开发php网站的html文件放在那个里面的
  • 济南网站建设抖音平台米拓建站模板
  • seo网站关键词优化费用优化网址
  • 为什么网站显示在建设中通州宋庄网站建设
  • 优秀网站建设空间麻将棋牌网站开发
  • 城乡建设查询网站长沙网页制作公司
  • 苏州高端网站建设咨询无锡新吴区建设局网站
  • 网站建设中 敬请期待.windows wordpress伪静态
  • 秦皇岛手机网站网站搜索引擎怎么做
  • 网站 维护费用关键词数据分析
  • 影响网站建设的关键点网站么做淘宝客赚佣金
  • 上海电信网站备案代理网页版
  • 建立一个同城网站要怎么做seo01
  • 网站海外推广外包网站买东西第三方怎么做
  • 刚做的网站 搜不到哪里有学网页设计
  • wordpress 浮动导航插件如何快速优化网站排名
  • 自己如何做网站统计建设网站方案
  • 海外域名提示风险网站吗亚洲电视全球运营中心
  • 江苏外贸网站建设推广动画师工资一般多少
  • 公司网站建设怎么计费wordpress进管理员密码
  • 尤溪住房和城乡建设局网站手机开网店的免费平台