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

网站建设找阿里云备案 网站备案

网站建设找,阿里云备案 网站备案,沈阳网站建设公司的公司,宁波外贸推广网络营销戳蓝字“CSDN云计算”关注我们哦#xff01;技术头条#xff1a;干货、简洁、多维全面。更多云计算精华知识尽在眼前#xff0c;get要点、solve难题#xff0c;统统不在话下#xff01;作者#xff1a;Trust_FreeDom转自#xff1a;码农沉思录本文主要讨论的是微服务注册… 戳蓝字“CSDN云计算”关注我们哦技术头条干货、简洁、多维全面。更多云计算精华知识尽在眼前get要点、solve难题统统不在话下作者Trust_FreeDom转自码农沉思录本文主要讨论的是微服务注册到Eureka注册中心并使用Zuul网关负载访问的情况如何停机可以使用户无感知。方式一kill -9 java进程id【不建议】kill -9 属于强杀进程首先微服务正在执行的任务被强制中断了其次没有通过Eureka注册中心服务下线Zuul网关作为Eureka Client仍保存这个服务的路由信息会继续调用服务Http请求返回500后台异常是Connection refuse连接拒绝。这种情况默认最长需要等待90s微服务在Eureka Server上租约到期30sEureka Server服务列表刷新到只读缓存ReadOnlyMap的时间Eureka Client默认读此缓存30sZuul作为Eureka Client默认每30秒拉取一次服务列表30sRibbon默认动态刷新其ServerList的时间间隔 180s即 3分钟总结此种方式既会导致正在执行中的任务无法执行完又会导致服务没有从Eureka Server摘除并给Eureka Client时间刷新到服务列表导致了通过Zuul仍然调用已停掉服务报500错误的情况不推荐。方式二kill -15 java进程id 或 直接使用/shutdown 端点【不建议】kill 与/shutdown 的含义首先kill等于kill -15根据man kill的描述信息The command kill sends the specified signal to the specified process or process group. If no signal is specified, the TERM signal is sent.即kill没有执行信号等同于TERM终止termination而kill -l查看信号编号与信号之间的关系kill -15就是 SIGTERMTERM信号给JVM进程发送TERM终止信号时会调用其注册的 Shutdown Hook当SpringBoot微服务启动时也注册了 Shutdown Hook而直接调用/shutdown端点本质和使用 Shutdown Hook是一样的所以无论是使用kill或 kill -15还是直接使用/shutdown端点都会调用到JVM注册的Shutdown Hook注意启用 /shutdown端点需要如下配置endpoints.shutdown.enabled trueendpoints.shutdown.sensitive false所有问题都导向了 Shutdown Hook会执行什么Spring注册的Shutdown Hook通过查询项目组使用Runtime.getRuntime().addShutdownHook(Thread shutdownHook)的地方发现ribbon注册了一些Shutdown Hook但这不是我们这次关注的我们关注的是Spring的应用上下文抽象类AbstractApplicationContext注册了针对整个Spring容器的Shutdown Hook在执行Shutdown Hook时的逻辑在AbstractApplicationContext#doClose()//## org.springframework.context.support.AbstractApplicationContext#registerShutdownHook /** * Register a shutdown hook with the JVM runtime, closing this context * on JVM shutdown unless it has already been closed at that time. * pDelegates to {code doClose()} for the actual closing procedure. * see Runtime#addShutdownHook * see #close() * see #doClose() */Overridepublic void registerShutdownHook() {if (this.shutdownHook null) {// No shutdown hook registered yet.// 注册shutdownHook线程真正调用的是 doClose()this.shutdownHook new Thread() {Overridepublic void run() { synchronized (startupShutdownMonitor) { doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); }}//## org.springframework.context.support.AbstractApplicationContext#doClose /** * Actually performs context closing: publishes a ContextClosedEvent and * destroys the singletons in the bean factory of this application context. * pCalled by both {code close()} and a JVM shutdown hook, if any. * see org.springframework.context.event.ContextClosedEvent * see #destroyBeans() * see #close() * see #registerShutdownHook() */protected void doClose() {if (this.active.get() this.closed.compareAndSet(false, true)) {if (logger.isInfoEnabled()) { logger.info(Closing this); }// 注销注册的MBean LiveBeansView.unregisterApplicationContext(this);try {// Publish shutdown event.// 发送ContextClosedEvent事件会有对应此事件的Listener处理相应的逻辑 publishEvent(new ContextClosedEvent(this)); }catch (Throwable ex) { logger.warn(Exception thrown from ApplicationListener handling ContextClosedEvent, ex); }// Stop all Lifecycle beans, to avoid delays during individual destruction.// 调用所有 Lifecycle bean 的 stop() 方法try { getLifecycleProcessor().onClose(); }catch (Throwable ex) { logger.warn(Exception thrown from LifecycleProcessor on context close, ex); }// Destroy all cached singletons in the contexts BeanFactory.// 销毁所有单实例bean destroyBeans();// Close the state of this context itself. closeBeanFactory();// Let subclasses do some final clean-up if they wish...// 调用子类的 onClose() 方法比如 EmbeddedWebApplicationContext#onClose() onClose();this.active.set(false); }}AbstractApplicationContext#doClose() 的关键点在于publishEvent(new ContextClosedEvent(this)) 发送ContextClosedEvent事件会有对应此事件的Listener处理相应的逻辑getLifecycleProcessor().onClose() 调用所有 Lifecycle bean 的 stop() 方法而ContextClosedEvent事件的Listener有很多实现了Lifecycle生命周期接口的bean也很多但其中我们只关心一个即 EurekaAutoServiceRegistration 它即监听了ContextClosedEvent事件也实现了Lifecycle接口EurekaAutoServiceRegistration的stop()事件//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistrationpublic class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {// lifecycle接口的 stop()Overridepublic void stop() {this.serviceRegistry.deregister(this.registration);this.running.set(false); // 设置liffecycle的running标示为false } // ContextClosedEvent事件监听器EventListener(ContextClosedEvent.class)public void onApplicationEvent(ContextClosedEvent event) {// register in case meta data changed stop(); } }如上可以看到EurekaAutoServiceRegistration中对 ContextClosedEvent事件 和 Lifecycle接口 的实现都调用了stop()方法虽然都调用了stop()方法但由于各种对于状态的判断导致不会重复执行如Lifecycle的running标示置为false就不会调用到此Lifecycle#stop()EurekaServiceRegistry#deregister()方法包含将实例状态置为DOWN 和 EurekaClient#shutdown() 两个操作其中状态置为DOWN一次后下一次只要状态不变就不会触发状态复制请求EurekaClient#shutdown() 之前也会判断AtomicBoolean isShutdown标志位下面具体看看EurekaServiceRegistry#deregister()方法EurekaServiceRegistry#deregister() 注销//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#deregisterOverridepublic void deregister(EurekaRegistration reg) {if (reg.getApplicationInfoManager().getInfo() ! null) {if (log.isInfoEnabled()) {log.info(Unregistering application reg.getInstanceConfig().getAppname() with eureka with status DOWN); }// 更改实例状态会立即触发状态复制请求 reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);//TODO: on deregister or on context shutdown// 关闭EurekaClient reg.getEurekaClient().shutdown(); }}主要涉及两步更新Instance状态为 DOWN 更新状态会触发StatusChangeListener监听器状态复制器InstanceInfoReplicator会向Eureka Server发送状态更新请求。实际上状态更新和Eureka Client第一次注册时都是调用的DiscoveryClient.register()都是发送POST /eureka/apps/appID请求到Eureka Server只不过请求Body中的Instance实例状态不同。执行完此步骤后Eureka Server页面上变成EurekaClient.shutdown() 整个Eureka Client的关闭操作包含以下几步PreDestroyOverridepublic synchronized void shutdown() {if (isShutdown.compareAndSet(false, true)) { logger.info(Shutting down DiscoveryClient ...);// 1、注销所有 StatusChangeListenerif ( statusChangeListener ! null applicationInfoManager ! null) { applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); }// 2、停掉所有定时线程实例状态复制、心跳、client缓存刷新、监督线程 cancelScheduledTasks();// If APPINFO was registered// 3、向Eureka Server注销实例if (applicationInfoManager ! null clientConfig.shouldRegisterWithEureka()) { applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN); unregister(); }// 4、各种shutdown关闭if (eurekaTransport ! null) { eurekaTransport.shutdown(); } heartbeatStalenessMonitor.shutdown(); registryStalenessMonitor.shutdown(); logger.info(Completed shut down of DiscoveryClient); }}其中应关注unregister()注销其调用AbstractJerseyEurekaHttpClient#cancel()方法向Eureka Server发送DELETE /eureka/v2/apps/appID/instanceID请求DELETE请求成功后Eureka Server页面上服务列表就没有当前实例信息了。注意 由于在注销上一步已经停掉了定时心跳线程否则注销后的下次心跳又会导致服务上线总结使用kill、kill -15 或 /shutdown端点都会调用Shutdown Hook触发Eureka Instance实例的注销操作这一步是没有问题的优雅下线的第一步就是从Eureka注册中心注销实例但关键问题是shutdown操作除了注销Eureka实例还会马上停止服务而此时无论Eureka Server端Zuul作为Eureka Client端都存在陈旧的缓存还未刷新服务列表中仍然有注销下线的服务通过zuul再次调用报500错误后台是connection refuse连接拒绝异常故不建议使用另外由于unregister注销操作涉及状态更新DOWN 和 注销下线 两步操作且是分两个线程执行的实际注销时根据两个线程执行完成的先后顺序最终在Eureka Server上体现的结果不同但最终效果是相同的经过一段时间的缓存刷新后此服务实例不会再被调用状态更新DOWN先结束注销实例后结束 Eureka Server页面清除此服务实例信息注销实例先结束状态更新DOWN后结束 Eureka Server页面显示此服务实例状态为DOWN方式三/pause 端点【可用但有缺陷】/pause 端点首先启用/pause端点需要如下配置endpoints.pause.enabled trueendpoints.pause.sensitive falsePauseEndpoint是RestartEndPoint的内部类//## Restart端点ConfigurationProperties(endpoints.restart)ManagedResourcepublic class RestartEndpoint extends AbstractEndpointBooleanimplements ApplicationListenerApplicationPreparedEvent { // Pause端点ConfigurationProperties(endpoints)public class PauseEndpoint extends AbstractEndpointBoolean {public PauseEndpoint() {super(pause, true, true); }Overridepublic Boolean invoke() {if (isRunning()) { pause();return true; }return false; } } // 暂停操作ManagedOperationpublic synchronized void pause() {if (this.context ! null) {this.context.stop(); } }}如上可见/pause端点最终会调用Spring应用上下文的stop()方法AbstractApplicationContext#stop()//## org.springframework.context.support.AbstractApplicationContext#stopOverridepublic void stop() {// 1、所有实现Lifecycle生命周期接口 stop() getLifecycleProcessor().stop(); // 2、触发ContextStoppedEvent事件 publishEvent(new ContextStoppedEvent(this));}查看源码并没有发现有用的ContextStoppedEvent事件监听器故stop的逻辑都在Lifecycle生命周期接口实现类的stop()而getLifecycleProcessor().stop() 与 方式二中shutdown调用的getLifecycleProcessor().doClose() 内部逻辑都是一样的都是调用了DefaultLifecycleProcessor#stopBeans()进而调用Lifecycle接口实现类的stop()如下Overridepublic void stop() { stopBeans();this.running false;}Overridepublic void onClose() { stopBeans();this.running false;}所以执行/pause端点 和 shutdown时的其中一部分逻辑是一样的依赖于EurekaServiceRegistry#deregister() 注销会依次执行触发状态复制为DOWN和Eureka Client注册上线register调用方法一样DiscoveryClient#register()发送POST /eureka/apps/appID请求到Eureka Server只不过请求Body中的Instance实例状态不同。执行完此步骤后Eureka Server页面上实例状态变成DOWN触发 EurekaClient.shutdown调用AbstractJerseyEurekaHttpClient#cancel()方法向Eureka Server发送DELETE /eureka/v2/apps/appID/instanceID请求DELETE请求成功后Eureka Server页面上服务列表就没有当前实例信息了。注意 由于在注销上一步已经停掉了定时心跳线程否则注销后的下次心跳又会导致服务上线1、注销所有 StatusChangeListener2、停掉所有定时线程实例状态复制、心跳、client缓存刷新、监督线程3、向Eureka Server注销实例4、各种shutdown关闭stop()执行完毕后Eureka Server端当前实例状态是DOWN还是下线取决于 状态DOWN的复制线程 和 注销请求 哪个执行快总结/pause端点可以用于让服务从Eureka Server下线且与shutdown不一样的是其不会停止整个服务导致整个服务不可用只会做从Eureka Server注销的操作最终在Eureka Server上体现的是 服务下线 或 服务状态为DOWN且eureka client相关的定时线程也都停止了不会再被定时线程注册上线所以可以在sleep一段时间待服务实例下线被像Zuul这种Eureka Client刷新到再停止微服务就可以做到优雅下线停止微服务的时候可以使用/shutdown端点 或 直接暴利kill -9注意我实验的当前版本下使用/pause端点下线服务后无法使用/resume端点再次上线即如果发版过程中想重新注册服务只有重启微服务。且为了从Eureka Server下线服务将整个Spring容器stop()也有点“兴师动众”/resume端点无法让服务再次上线的原因是虽然此端点会调用AbstractApplicationContext#start() --EurekaAutoServiceRegistration#start() --EurekaServiceRegistry#register()但由于之前已经停止了Eureka Client的所有定时任务线程比如状态复制 和 心跳线程重新注册时虽然有maybeInitializeClient(eurekaRegistration)尝试重新启动EurekaClient但并没有成功估计是此版本的Bug导致UP状态并没有发送给Eureka Server可下线无法重新上线方式四/service-registry 端点【可用但有坑】/service-registry 端点首先在我使用的版本 /service-registry 端点默认是启用的但是是sensitive 的也就是需要认证才能访问我试图找一个可以单独将/service-registry的sensitive置为false的方式但在当前我用的版本没有找到/service-registry端点是通过ServiceRegistryAutoConfiguration自动配置的 ServiceRegistryEndpoint而 ServiceRegistryEndpoint这个MvcEndpoint的isSensitive()方法写死了返回true并没有给可配置的地方或者自定义什么实现然后在ManagementWebSecurityAutoConfiguration这个安全管理自动配置类中将所有这些sensitivetrue的通过Spring Security的httpSecurity.authorizeRequests().xxx.authenticated()设置为必须认证后才能访问目前我找到只能通过 management.security.enabledfalse 这种将所有端点都关闭认证的方式才可以无认证访问# 无认证访问 /service-registry 端点management.security.enabledfalse更新远端实例状态/service-registry端点的实现类是ServiceRegistryEndpoint其暴露了两个RequestMapping分别是GET 和 POST请求的/service-registryGET请求的用于获取实例本地的status、overriddenStatusPOST请求的用于调用Eureka Server修改当前实例状态ManagedResource(description Can be used to display and set the service instance status using the service registry)SuppressWarnings(unchecked)public class ServiceRegistryEndpoint implements MvcEndpoint {private final ServiceRegistry serviceRegistry;private Registration registration;public ServiceRegistryEndpoint(ServiceRegistry? serviceRegistry) {this.serviceRegistry serviceRegistry; }public void setRegistration(Registration registration) {this.registration registration; }RequestMapping(path instance-status, method RequestMethod.POST)ResponseBodyManagedOperationpublic ResponseEntity? setStatus(RequestBody String status) { Assert.notNull(status, status may not by null);if (this.registration null) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(no registration found); }this.serviceRegistry.setStatus(this.registration, status);return ResponseEntity.ok().build(); }RequestMapping(path instance-status, method RequestMethod.GET)ResponseBodyManagedAttributepublic ResponseEntity getStatus() {if (this.registration null) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(no registration found); }return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration)); }Overridepublic String getPath() {return /service-registry; }Overridepublic boolean isSensitive() {return true; }Overridepublic Class? extends Endpoint? getEndpointType() {return null; }}我们关注的肯定是POST请求的/service-registry如上可以看到其调用了EurekaServiceRegistry.setStatus() 方法更新实例状态//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistrypublic class EurekaServiceRegistry implements ServiceRegistryEurekaRegistration { // 更新状态Overridepublic void setStatus(EurekaRegistration registration, String status) { InstanceInfo info registration.getApplicationInfoManager().getInfo();// 如果更新的status状态为CANCEL_OVERRIDE调用EurekaClient.cancelOverrideStatus()//TODO: howto deal with delete properly?if (CANCEL_OVERRIDE.equalsIgnoreCase(status)) { registration.getEurekaClient().cancelOverrideStatus(info);return; }// 调用EurekaClient.setStatus()//TODO: howto deal with status types across discovery systems? InstanceInfo.InstanceStatus newStatus InstanceInfo.InstanceStatus.toEnum(status); registration.getEurekaClient().setStatus(newStatus, info); } }EurekaServiceRegistry.setStatus() 方法支持像Eureka Server发送两种请求分别是通过 EurekaClient.setStatus() 和EurekaClient.cancelOverrideStatus() 来支持的下面分别分析EurekaClient.setStatus()实际是发送 PUT /eureka/apps/appID/instanceID/status?valuexxx到Eureka Server这是注册中心对于 Take instance out of service 实例下线 而开放的Rest API可以做到更新Eureka Server端的实例状态status 和 overriddenstatus一般会在发版部署时使用让服务下线更新为OUT_OF_SERVICE由于overriddenstatus更新为了OUT_OF_SERVICE故即使有 心跳 或 UP状态复制也不会改变其OUT_OF_SERVICE的状态overriddenstatus覆盖状态就是为了避免服务下线后又被定时线程上线或更新状态而设计的有很多所谓的 “覆盖策略”也正是由于overriddenstatus覆盖状态无法被 心跳 和 UP状态复制其实就是EurekaClient.register()而影响故在发版部署完新版本后最好先调用Rest API清除overriddenstatus再启动服务如果直接启动服务可能导致Server端仍是OUT_OF_SERVICE状态的问题实验 更新状态为OUT_OF_SERVICE后直接停服务只有等到Server端服务租约到期下线后再启动客户端上线才能成功注册并状态为UP如果没等Server端下线服务不存在后就启动服务注册上线后无法改变overriddenstatusOUT_OF_SERVICEEurekaClient.cancelOverrideStatus() 实际是发送 DELETE /eureka/v2/apps/appID/instanceID/status 到Eureka Server用于清除覆盖状态其实官方给出的是 DELETE /eureka/v2/apps/appID/instanceID/status?valueUP其中valueUP可选是删除overriddenstatus为UNKNOWN之后建议status回滚为什么状态但我当前使用版本里没有这个 valueUP可选参数就导致发送后Eureka Server端 statusUNKNOWN 且 overriddenstatusUNKNOWN但UNKNOWN覆盖状态不同的事虽然心跳线程仍对其无作用但注册等同于UP状态更新是可以让服务上线的总结/service-registry端点可以更新服务实例状态为 OUT_OF_SERVICE再经过一段Server端、Client端缓存的刷新使得服务不会再被调用此时再通过/shutdown端点 或 暴利的kill -9 停止服务进程可以达到优雅下线的效果如希望回滚可以通过几种方式还是/service-registry端点只不过状态为 CANCEL_OVERRIDE具体逻辑在 EurekaServiceRegistry.setStatus() 中其等同于直接调用Eureka Server API  DELETE /eureka/v2/apps/appID/instanceID/status可以让Server端 statusUNKNOWN 且 overriddenstatusUNKNOWN也可以用 /service-registry端点状态为UP可使得Server端 statusUP且 overriddenstatusUP虽然可以临时起到上线目的但 overriddenstatusUP 仍需要上一步的DELETE请求才能清楚很麻烦不建议使用不通过Eureka Client的端点直接调用Eureka Server端点 DELETE /eureka/apps/appID/instanceID/status?valueUP实际使用过程中建议如下顺序缓存刷新时间 指的是Eureka Server刷新只读缓存、Eureka Client刷新本地服务列表、Ribbon刷新ServerList的时间默认都是30s可以适当缩短缓存刷新时间# Eureka Server端配置eureka.server.responseCacheUpdateIntervalMs5000eureka.server.eviction-interval-timer-in-ms5000# Eureka Client端配置eureka.client.registryFetchIntervalSeconds5ribbon.ServerListRefreshInterval5000单个请求处理时间 是为了怕服务还有请求没处理完1、调用/service-registry端点将状态置为 OUT_OF_SERVICE2、sleep 缓存刷新时间 单个请求处理时间3、调用 /service-registry端点将状态置为 CANCEL_OVERRIDE其实就是向Server端发送DELETE overriddenstatus的请求这会让Server端 statusUNKNOWN 且 overriddenstatusUNKNOWN4、使用 /shutdown端点 或 暴利kill -9终止服务5、发版部署后启动服务注册到Eureka Server服务状态变为UP方式五 直接调用Eureka Server Rest API【可用但URL比较复杂】上面说了这么多其实这些都是针对Eureka Server Rest API在Eureka客户端上的封装即通过Eureka Client服务由于引入了actuator增加了一系列端点其实一些端点通过调用Eureka Server暴露的Rest API的方式实现Eureka实例服务下线功能Eureka Rest API包括其中大多数非查询类的操作在之前分析Eureka Client的端点时都分析过了其实调用Eureka Server的Rest API是最直接的但由于目前多采用一些类似Jenkins的发版部署工具其中操作均在脚本中执行Eureka Server API虽好但URL中都涉及appID 、instanceID对于制作通用的脚本来说拼接出调用端点的URL有一定难度且不像调用本地服务端点IP使用localhost 或 127.0.0.1即可需要指定Eureka Server地址所以整理略显复杂。不过在比较规范化的公司中也是不错的选择。 福利扫描添加小编微信备注“姓名公司职位”加入【云计算学习交流群】和志同道合的朋友们共同打卡学习推荐阅读太形象了什么是边缘计算最有趣的解释没有之一互联网出海十年华为员工年薪 200 万真相让人心酸天才程序员25 岁进贝尔实验室32 岁创建信息论  琥珀  极客宝宝  5天前安全顾问反水成黑客, 靠瞎猜盗得5000万美元的以太币, 一个区块链大盗的另类传奇人造器官新突破美国科学家3D打印出会“呼吸”的肺 | Science真香朕在看了
http://wiki.neutronadmin.com/news/134743/

相关文章:

  • 西宁做网站君博专注房产中介网站建设技巧
  • 什么都不会怎么做网站wordpress生成的网页
  • 浙江省住房建设厅网站百度可以建网站吗
  • 网站建设网络推广方案想学互联网运营从哪里入手
  • 服务器用来做网站和数据库太原手机网站开发
  • 郑州做网站 艾特传奇世界网游
  • 网站变灰 兼容贵阳网站建设q479185700惠
  • 怎么在百度上做网站什么域名不用备案
  • 体育用品网站模板虞城网站建设
  • 河南省做网站的公司网站建设 石家庄
  • 做盗版影视网站wordpress瀑布流图片主题
  • 建设银行信用卡网站登录网站建设工作稳定吗
  • seo 网站文案模板企业查天眼查在线
  • 生物技术网站开发零食网站建设策划书模板
  • 网站建设 费用预算做购物网站收费标准
  • 网站首页全屏怎么做一个工厂的网站建设
  • 做网站还是做微信公众号做宣传册的公司
  • 做网站需要哪些费用聊大 网站设计
  • 淘宝网站SEO怎么做可使用虚拟主机
  • 国外建设短视频网站利辛做网站
  • 移动通信网站建设wordpress侧栏弹窗登录
  • 企业网站代码中国互联网协会官方网站
  • icp备案在哪里查询图片类网站 怎么做优化
  • 深圳做微商网站设计免费软件看电视剧
  • 做网站用别人的模板是侵权吗新手学做网站电子版
  • 番禺人才网上seo优化工具的种类
  • 深色网站如何防止网站被盗
  • 做什么网站比较简单中企动力百度百科
  • 人事处网站开发文献综述做跨境电商需要多少钱
  • 响应式网站开发现状松江微网站建设