开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

快速搭建 SpringCloud 微服务开发环境的脚手架

发表于 2019-11-27

本文作者:HelloGitHub-秦人

本文适合有 SpringBoot 和 SpringCloud 基础知识的人群,跟着本文可使用和快速搭建 SpringCloud 项目。

HelloGitHub 推出的《讲解开源项目》系列,今天给大家带来一款基于 SpringCloud2.1 的微服务开发脚手开源项目——SpringCloud

项目源码地址:github.com/zhoutaoo/Sp…

一、微服务的简介

微服务是可以独立部署、水平扩展、独立访问的服务单元。Java 中常见最小的微服务单元就是基于 SpringBoot 框架的一个独立项目。一个微服务只做一件事(单一职责),多个微服务组合才能称之为一个完整的项目或产品。那么多个微服务的就需要来管理,而 SpringCloud 就是统筹这些微服务的大管家。它是一系列有序框架的集合,简单易懂、易部署易维护的分布式系统开发工具包。

今天介绍的开源项目就是基于 SpringCloud2.1 的脚手架,让项目开发快速进入业务开发,而不需过多时间花费在架构搭建上,下面就让我们一起来看看这个项目的使用吧。

二、项目结构

这里以一个网关(gateway-admin)微服务来说明。

项目目录结构如下图:

目录说明:

  1. db:项目初始化数据库脚本。
  2. docker:Docker 配置文件目录,将微服务打包为 docker 镜像(image)。
  3. config:项目配置信息目录,包括数据库配置,消息转化配置等。
  4. dao:数据库操作目录,主要对底层数据进行增删查改。
  5. entity:项目实体类目录。
  6. events:事件处理目录。
  7. exception:异常处理目录,通过面向切面处理全局异常。
  8. rest:微服务控制器目录,也就是对外提供的接口。
  9. service:微服务业务层目录。
  10. GatewayAdminApplication:微服务 SpringBoot 入口类。
  11. resources:项目配置文件目录。
  12. test:项目单元测试目录。
  13. pom.xml:maven 项目对象模型文件。

三、实战操作

3.1 前提

  • 确保本地安装 Git、Java8、Maven。
  • 懂一些 SpringMVC 的知识,因为 SpringBoot 是基于 SpringMVC 演化而来的。
  • 懂一些应用容器引擎 Docker、Docker-compose 的知识。

3.2 微服务架构说明

一个完整的项目,微服务架构一般包括下面这些服务:

  • 注册中心(常用的框架 Nacos、Eureka)
  • 统一网关(常用的框架 Gateway、Zuul)
  • 认证中心(常用技术实现方案 Jwt、OAuth)
  • 分布式事务(常用的框架 Txlcn、Seata)
  • 文件服务
  • 业务服务

3.3 运行项目

下面介绍了三种运行的方式:

第一种:一键运行

Linux 和 Mac 系统下可在项目根目录下执行 ./install.sh 快速搭建开发环境。

第二种:本地环境运行

不推荐此方法,但还是简单介绍下。

  1. 基础环境安装:mysql、redis,rabbitmq
  2. 环境运行:
1
复制代码git clone https://github.com/zhoutaoo/SpringCloud.git #克隆项目
  1. 安装认证公共包到本地 maven 仓库,执行如下命令:
1
2
复制代码cd common
mvn clean install #安装认证公共包到本地 maven 仓库
  1. 安装注册中心 Nacos
* 下载 [Nacos](https://github.com/alibaba/nacos/releases)
* 执行如下命令:
1
2
3
4
复制代码unzip nacos-server-0.9.0.zip  OR tar -xvf nacos-server-0.9.0.tar.gz
cd nacos/bin
bash startup.sh -m standalone # Linux 启动命令
cmd startup.cmd # Windows 启动命令
  1. 运行网关服务、认证服务、业务服务等

这里以网关服务为例:执行 GatewayAdminApplication.java

注意:认证服务(auth)、网关服务(gateway)、组织管理服务(sysadmin)需要执行数据库初始化脚本。

可通过 swager 接口:http://localhost:8445/swagger-ui.html 测试是否搭建成功,如果能正常访问表示服务启动成功。

说明:

  • application.yml 文件主要配置 rabbitmq,redis, mysql 的连接信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码spring:
rabbitmq:
host: ${RABBIT_MQ_HOST:localhost}
port: ${RABBIT_MQ_PORT:5672}
username: ${RABBIT_MQ_USERNAME:guest}
password: ${RABBIT_MQ_PASSWORD:guest}
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
#password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 300

datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:${DATASOURCE_DBTYPE:mysql}://${DATASOURCE_HOST:localhost}:${DATASOURCE_PORT:3306}/sc_gateway?characterEncoding=UTF-8&useUnicode=true&useSSL=false
username: ${DATASOURCE_USERNAME:root}
password: ${DATASOURCE_PASSWORD:root123}
  • bootstrap.yml 文件主要配置服务基本信息(端口,服务名称),注册中心地址等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码server:
port: ${SERVER_PORT:8445}
spring:
application:
name: gateway-admin
cloud:
nacos:
discovery:
server-addr: ${REGISTER_HOST:localhost}:${REGISTER_PORT:8848}
config:
server-addr: ${REGISTER_HOST:localhost}:${REGISTER_PORT:8848}
file-extension: yml
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:localhost}:${SENTINEL_DASHBOARD_PORT:8021}

第三种:Docker 环境运行

  1. 基础环境安装
* 通过 docker 命令安装



1
2
3
4
5
6
复制代码# 安装redis
docker run -p 6379:6379 --name redis -d docker.io/redis:latest --requirepass "123456"
# 安装mysql
docker run --name mysql5.7 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root123 -d docker.io/mysql:5.7
# 安装rabbitmq
docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq docker.io/rabbitmq:latest
* 也可以通过 docker-compose 命令安装
1
2
复制代码cd docker-compose
docker-compose up -d #docker-compose 安装mysql,redis,rabbitmq 服务
  1. 下载项目到本地
1
复制代码git clone https://github.com/zhoutaoo/SpringCloud.git #克隆项目
  1. 安装认证公共包到本地 maven 仓库执行如下命令:
1
复制代码cd common && mvn install #安装认证公共包到本地maven仓库
  1. docker-compose 运行 Nacos
1
2
复制代码cd docker-compose
docker-compose -f docker-compose.yml -f docker-compose.nacos.yml up -d nacos #启动注册中心
  1. 构建消息中心镜像
1
2
3
4
5
复制代码cd ./center/bus
mvn package && mvn docker:build
cd docker-compose
#启动消息中心
docker-compose -f docker-compose.yml -f docker-compose.center.yml up -d bus-server

需要构建镜像的其他服务有:(注:操作和消息中心镜像构建方式类似)

  • 网关管理服务 (gateway-admin、gateway-web)
  • 组织服务(sysadmin/organization)
  • 认证服务 (auth/authentication-server)
  • 授权服务(auth authorization-server)
  • 管理台服务(monitor/admin)

3.4 运行效果

Nacos 服务中心

所有服务都正常启动,在 nacos 管理中心可查看,实例数表示运行此服务的个数,值为 1 可以理解为服务正常启动。

查看后台服务

命令行执行:docker ps -a 查看 docker 所有进程信息

通过访问微服务对外暴露的接口(swagger)检测服务是否可用。

swager 接口地址:http://IP:port/swagger-ui.html

测试如下图:

四、最后

微服务(SpringBoot、SpringCloud、Docker)现在吵得特别火,它并不是一门新的技术,而是在老技术的基础上衍生出来的,增加了一些新的特性。

教程至此,你应该能够通过 SpringCloud 这项目快速搭建微服务了。那么就可以开始你的微服务学习之旅了,是时候更新一下自己的技能树了,让我们一起来学习微服务吧!

五、参考资料

  • 本地安装 Nacos
  • nacos注册中心的使用
  • docker-compose教程
  • Docker技术

『讲解开源项目系列』——让对开源项目感兴趣的人不再畏惧、让开源项目的发起者不再孤单。跟着我们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎留言联系我们、加入我们,让更多人爱上开源、贡献开源~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

手把手教你优雅的编写第一个SpringMVC程序

发表于 2019-11-27

可能之前写的文章走进SpringMVC世界,从SpringMVC入门到SpringMVC架构中的第一个springMVC入门程序讲解的不是那么优雅、细致、精巧,因此特地写这篇稍微优雅、细致、精巧一些的文章,至于优雅、细致、精巧到什么程度呢,我觉得只要有servlet的基础应该就能懂(哈哈,好像有点夸大其词了),反正应该大概可能会适合小白(我不是大牛,我也是小白QnQ)

首先,看一下传统至今的数据交互的常规套路,嗯,还是原来的味道,真香!

在这里插入图片描述

其次,要明确springMVC是充当MVC中的哪个角色,现在我要说的是第一个SpringMVC程序是以jsp ->Servlet (Springmvc)->Jsp流程写的,一眼看出,是collection层!于是从servlet入手。

普通的servlet流程: 请求-url-pattern -交给对应的servlet去处理

如果现在想用springmvc,而不是普通的servlet,如何告知程序?-如何让springmvc 介入程序:

Springmvc需要配置一个 Springmvc自带的servlet

通过以下配置,拦截所有请求,交给SpringMVC处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码 <servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

详解看下图:

在这里插入图片描述

其中需要注意以下的拦截规则:

< url-pattern > .action < /url-pattern >

/:一切请求 ,注意不是 /*
/user: 拦截以 /user开头的请求
/user/abc.do: 只拦截该请求
.action: 只拦截 .action结尾的请求

.action为例:

在这里插入图片描述

至于init-param的内容,通过源码发现…(不说废话了直接看图哈哈)

在这里插入图片描述

通过init-param属性配置指定springmvc配置文件的路径,如果要省略,必须放到 默认路径:
/WEB-INF/springDispatcherServlet-servlet.xml
在这里插入图片描述

在这里,考验一下自己,有没有什么办法能知道默认路径为/WEB-INF/springDispatcherServlet-servlet.xml的,先自己想想。其实很简单,我们只要把init-param配置一删,运行Tomcat,报错,看异常,这一流程下来你就会发现异常就很明显的告诉我们默认路径了,如下图:
在这里插入图片描述

当然,项目中同时兼容 springMVC版的servlet和普通版的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码 <servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>.action</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

接下来创建一个后缀名为handler的普通类,要想把一个普通类变的不普通有几种方法:接口/类 、 注解 、 配置 ,而springMVC用注解则显得时尚,所以采用了注解,特别注意@RequestMapping的用法,因为之后它会非常常见!!!

在这里插入图片描述

当然不要忘记给这个类组件扫描,不然springMVC是不认识它的哦,为了方便直接扫描了所在整个包
在这里插入图片描述

到这里,servlet就完成了,接下来是JSP了
在这里插入图片描述

再接下来回到springMVC.xml配置中,要再学习一个好东西那就是InternalResourceViewResolver视图解析器,首先观察源码,如下图:
在这里插入图片描述

在这里插入图片描述

这个时候,你再写请求时,直接写success的时候,默认就会给你解析为如下图;
在这里插入图片描述

到这里,基本上就大功告成了,于是到了胆战心惊的测试程序环节了,紧张又刺激!

在这里插入图片描述

好了,最后一起来分析分析一下我们的第一个springMVC的入门程序的整体流程吧!
在这里插入图片描述

什么?要源码。。。额,好的呢
源码已打包至网盘
什么?没用过百度网盘?
出门右拐————秋名山决斗在这里插入图片描述

链接:pan.baidu.com/s/1JRdk1RsP…
提取码:5d78
到这里,恭喜各位,贺喜大家,现在的everybody完美地走上了SpringMVC的不归之路!一条从入门到入土的道路(话好像不太好听,但毕竟学习编程嘛,活到老,编程也就到老!

如果本文对你有一点点帮助,那么请点个赞呗,谢谢~

最后,若有不足或者不正之处,欢迎指正批评,感激不尽!如果有疑问欢迎留言,绝对第一时间回复!

欢迎各位关注我的公众号,一起探讨技术,向往技术,追求技术,说好了来了就是盆友喔…

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Docker系列-(3) Docker-compose使用与

发表于 2019-11-26

docker.png

目前已经更新完《Java并发编程》和《Docker教程》,欢迎关注【后端精进之路】,轻松阅读全部文章。

后端精进之路.png

Java并发编程:

  • Java并发编程系列-(1) 并发编程基础
  • Java并发编程系列-(2) 线程的并发工具类
  • Java并发编程系列-(3) 原子操作与CAS
  • Java并发编程系列-(4) 显式锁与AQS
  • Java并发编程系列-(5) Java并发容器
  • Java并发编程系列-(6) Java线程池
  • Java并发编程系列-(7) Java线程安全
  • Java并发编程系列-(8) JMM和底层实现原理
  • Java并发编程系列-(9) JDK 8/9/10中的并发

Docker教程:

  • Docker系列-(1) 原理与基本操作
  • Docker系列-(2) 镜像制作与发布
  • Docker系列-(3) Docker-compose使用与负载均衡

JVM性能优化:

  • JVM性能优化系列-(1) Java内存区域
  • JVM性能优化系列-(2) 垃圾收集器与内存分配策略

上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡。

Docker-compose介绍

实际开发过程中,在一个项目中,我们常将不同的模块放在单独的docker中,方便维护和扩展。比如我们一个项目可能有MySQL镜像、Nginx镜像、Spring Boot后端镜像,我们在实际部署中可以采用上篇文章介绍的方法,分别打包,分别启动;但是这样太费精力,而且还容易出错。
因此Docker-compose工具应运而生,就是为了解决工程部署中的多个Docker镜像的管理问题。

docker-compose.yaml 文件

类似于build镜像需要Dockerfile一样,使用docker-compose时也需要类似的配置文件,叫做docker-compose.yaml。

下面是一个Docker-file的例子:


一般docker-compose文件中需要注意如下几点:

  • version: ‘3’: 表示使用第三代语法,每代的语法稍有不同,我们选择最新的就好。
  • services: 表示compose需要启动的服务,一般一个docker镜像就可以作为一个单独的service。
  • container_name: 容器名称
  • environment: 容器环境变量,可以使用这个参数往容器传递一些变量,这个在我们切换不同环境配置时非常有用,不用每次去改变代码中的配置
  • ports: 对外开放的端口
  • restart: always表示如果服务启动不成功一直尝试。
  • volumes: 加载本地目录到容器目标路径,也就是将本地路径共享给容器,方便进行数据交互,也可以利用这个功能,将docker的log输出到本机指定的位置。
  • depends_on:依赖服务,先启动 depends_on 服务。
  • command: mvn clean spring-boot:run : 表示以这个命令来启动项目

上面提到了depends_on,通常用于需要顺序启动不同镜像,比如后台需要等待数据库初始化完毕以后才启动,但是这个depends_on并不能保证前序容器完全初始化,意思是只要前序容器启动了,就会尝试启动当前容器。这样还是会造成前序容器未初始化完成,后续容器启动后出现连接失败等错误。

因此最好的方式是官方(docs.docker.com/compose/sta…)比较推荐的wait脚本,脚本的详细使用可以看这里 github.com/vishnubob/w…,

1
2
3
4
5
6
7
8
9
复制代码wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes

一般也可以在制作镜像的时候指定等待一定时间,在下面的例子中,在运行test.jar之前,会一直等到elasticsearch初始化完成。

1
2
3
4
5
6
复制代码FROM openjdk:8
ADD ["target/test-1.0.0.jar", "test.jar"]
ADD ["wait-for-it.sh", "wait-for-it.sh"]
RUN ["chmod", "+x", "wait-for-it.sh"]
EXPOSE 1234
ENTRYPOINT ["./wait-for-it.sh", "elastic:9200", "--", "java", "-jar", "/test.jar"]

关于更多docker-compose文件的详细介绍,可以参考官方的文档 docs.docker.com/compose/get…

docker-compose的启动/停止

docker-compose的启动与停止非常简单,直接使用docker-compose up启动,docker-compose down关闭工程,同时会删除所有容器。

Docker的网络通信

docker的网络方面是相对较弱的,新手如果不清楚,经常会出现为什么容器A访问不到容器B的问题。

容器间的通信

有个很常见的场景,我们需要在本机同时调试前端和后端程序,通常我们会在localhost某个端口启动前端,比如8080;同时在local的另一端口比如1234启动后端。如果不用容器,前端直接在代码里访问localhost:1234的所有接口都是可以的,没有任何问题。

但是如果前端和后端都封装在不同的容器中,这个时候前端直接访问localhost:1234,会报错,无法访问。原因就是两个容器之间是独立的,无法直接localhost访问。

官方推荐的解决办法就是,先新建一个网络,然后启动前后端容器的时候,分别指定在该网络中运行。

1
2
3
复制代码docker network create mynet
docker run --network mynet --name frontend ...
docker run --network mynet --name backend ...

现在前端就可以通过 http://backend:1234 访问后端的接口了。

注意:有些博客指出,可以通过docker inspect [container_name]来获取后端容器的ip,然后直接访问ip加端口就可以了。这个在Linux上也许可行,但是mac下由于容器的实现原理不同,无法直接通过ip访问。

docker-compose下的网络环境

默认情况下,当你利用docker-compose启动多个容器时,docker-compose会自动帮你创建一个网络,并将多个service在这个网络中运行。因此不同容器之间,直接利用容器名+端口就可以访问了。

Docker-compose实现负载均衡

实际生产环境中,肯定会启动多个后端service,同时进行负载均衡以保证服务质量。利用docker-compose我们可以方便的实现这种功能。


下面是一个很简单的docker-compose文件,启动之后可以在本地打开localhost:5000,来访问后端的service;在front容器内部,我们也可以通过backend:5000来访问backend service。

1
2
3
4
5
6
7
8
9
复制代码version: "3"

services:
front:
image: front:latest
backend:
image: "backend:latest"
ports:
- "5000:5000"

为了启动多个后端程序以实现负载均衡,docker-compose已经为我们提供--scale参数,利用这个参数可以轻松实现同时启动多个service。
下面是该参数的使用说明。

1
2
复制代码--scale SERVICE=NUM        Scale SERVICE to NUM instances. Overrides the
`scale` setting in the Compose file if present.

在这里,我们直接在启动的时候,指定生成3个后端的service,

1
复制代码docker-compose up --scale backend=3

但是这样会报错,因为本机上的5000端口,只能分配给一个service,如果你想同时在本机上调试,可以将上面的ports改为5000,修改后的docker-compose文件如下:

1
2
3
4
5
6
7
8
9
复制代码version: "3"

services:
front:
image: front:latest
backend:
image: "backend:latest"
ports:
- "5000"

这样按照上面同样的指令启动之后,你你可以看到如下的输出结果,有3个不同的本机端口绑定到容器的5000端口,

1
2
3
4
5
复制代码      Name                      Command                  State                Ports
--------------------------------------------------------------------------------------------
example_backend_1 /usr/bin/dumb-init -- /sbi ... Up (healthy) 0.0.0.0:32776->5000/tcp
example_backend_2 /usr/bin/dumb-init -- /sbi ... Up (healthy) 0.0.0.0:32775->5000/tcp
example_backend_3 /usr/bin/dumb-init -- /sbi ... Up (healthy) 0.0.0.0:32777->5000/tcp

当然,如果你只想从容器内部访问后端的5000端口,你可以去掉port配置,改成如下的配置:

1
2
3
4
5
6
7
复制代码version: "3"

services:
front:
image: front:latest
backend:
image: "backend:latest"

这样docker-compose up --scale backend=3启动后,我们就只能在容器内部访问http:\\backend:5000来访问后端service,因为我们启动了三个,前端的请求会随机的分配到后端的三个service上,保证了整体的服务质量。

参考链接:

  • stackoverflow.com/questions/4…
  • pspdfkit.com/blog/2018/h…

搜索『后端精进之路』并关注,立刻获取最新文章和价值2000元的BATJ精品面试课程。

后端精进之路.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么说软件服务的未来必然是WebAssembly?

发表于 2019-11-26

WebAssembly(WASM)最初由W3C 的Mozilla、谷歌、微软、苹果等著名公司合作研发,是浏览器内应用程序的高性能执行引擎。随着WASM 在开发者社区中越来越流行,WASM 也正在从客户端迁移到服务端,成为服务端技术的新锐。

价值定位

WASM 从客户端到服务端的迁移,与Java 和 JavaScript 等前几代技术的发展历程一脉相承。与现有解决方案相比,WASM 在服务端提供了一些重要的价值。

与Java 和 JavaScript 引擎相比,WASM 通过LLVM 工具链支持20多种编程语言,从而让开发人员有选择语言工具的自由,大大提高了生产力。特别是,它支持有影响力的新锐编程语言,例如Rust 。同时,WASM 可以轻松支持CPU、GPU、 AI 优化芯片和FPGA 等硬件功能。

它提供了接近于本机代码的性能,而又不牺牲安全性。

图片来源:steveklabnik.github.io/booster2018…
与Docker之类的容器相比,它为开发人员提供了更抽象的代码执行环境,从而提高了开发效率。它可以直接部署代码和应用程序,而不必启动操作系统。Wasm 程序不需修改,就可以在不同的操作系统上运行。这与当今的云原生微服务架构配合得很好。

WASM 消耗的内存和资源比操作系统级别的容器少得多。其模块化的安全模型允许为每个函数调用配置硬件和操作系统资源,从而在保证安全性的同时,保留了本机性能。

如果在 2008 年已经有了 WASM + WASI,那么我们压根无需创始 Docker 这个项目了。 Wasm 就有这么重要!服务器上的 WebAssembly 是计算的未来。 — Docker 联合创始人 Solomon Hykes

此外,服务器端 WASM 受益于这几年源于区块链社区的重大创新。

区块链先行一步

服务器端 WASM 目前处于早期阶段,还有大量的工作亟待完成,包括优化、开发工具和集成库,然后才能在企业中大规模应用。但是,开发者已经在企业计算、去中心化的区块链应用等新兴领域中将其用于微服务。

区块链智能合约是不可更改并且自动执行的代码。去中心化的应用(或 dapps )可以通过 Web 界面与智能合约进行交互。这使得智能合约成为自然的微服务。

以太坊,EOS 和 Polkadot 等大型公共区块链网络(公链)都致力于采用 WASM 作为其下一代执行引擎。因此,公链是当今编写和部署 WASM 服务的最佳平台。

服务端 WASM 受益于近年来投入到区块链领域的数十亿美元的研发投资。这些区块链研发创新的许多都与企业应用高度相关。

  • 执行结果确定的应用程序
  • 对共享资源进行操作码级别的准确计费
  • 对计算结果的无信任共识
  • 对数字货币基础设施的支持
  • 应用程序的形式化验证
  • 零知识计算

让代码说话

区块链应用已经做出了可演示的去中心化的微服务 Demo。以在 Second State 的 BUIDL 开发工具开发区块链应用为例,花上几分钟就可以在区块链网络上部署微服务,然后在此基础上构建一个去中心化的 Web 应用程序。

BUIDL 中的示例程序的“后端”是区块链智能合约(微服务)。智能合约是去中心化与云原生的。

Second State 的 BUIDL 还能将 WASM 智能合约部署到以太坊基金会的 Ewasm 测试网上。

服务端 WASM 的下一个爆发点是区块链之外的企业应用。在数据中心里,WASM 提供多种语言的服务(支持服务端上的 Rust !),支持多种硬件和操作系统平台,占用很少计算资源,并且可以直接触达底层硬件(比如 GPU 和 AI 芯片)。您可以在此处查看在WASM中运行Rust服务的概念演示。

前方之路

即将发布的 Second State 虚拟机(SSVM)是为服务端应用程序优化的 WASM。

在区块链领域,Second State 已经走在了世界最前沿,为下一代以太坊兼容的区块链系统提供虚拟机(Ewasm)。用于区块链的 SSVM 原生支持256位整数运算。与其他 Ewasm 实现相比,SSVM 在虚拟机内部拒绝执行结果不确定的操作码,如浮点运算,同时也实现了对 gas 的精确计算,从而大大提高了性能。

在云服务领域,SSVM 优化了 WASM 针对企业的功能,例如程序状态的高效存储,高性能输入输出,多线程支持,以及对硬件加速器的直接支持。 通过与高通公司的合作,SSVM 在高通芯片上运行时,可以直接访问高通芯片的 AI SDK。

服务端 WASM 的发展前景充满了希望。

加入我们,向下一代云架构迈进!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

策略模式&模板模式&工厂模式 如何优雅地用在项目中

发表于 2019-11-26

关于策略模式、模板模式和工厂模式的基础概念和优缺点可以自行了解一下,这里主要讲的是如何优雅地使用这三种模式保证服务符合:SRP(单一职责原则)和OCP(开闭原则)、耦合度低、可扩展性高和减少大量if else代码的场景。

策略模式:

1.环境(Context)角色:持有一个Strategy的引用。

2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

这种的经典并简单的策略模式大家也许已经使用过了,但是这样的策略模式会有一个缺点:

策略模式适用于多类型场景,调用策略时必定会有大量的if else,后续如有新的类型的策略需要被使用时则需要增加if else,代码改动较大,从而导致该模块可扩展性不高且会产生大量if else代码,不易维护。

为更好体验到优化的过程,首先给一个需求背景:

某服务需要展示给用户三种不同的趋势图:指数变化趋势图、新增课件数趋势图、新增点评数趋势图。

模块优化Round 1:

为了解决以上的问题,使用策略模式+自定义注解+模板模式(模板模式和优化无关,只是业务需要)来设计一下:

首先需要抽象父类策略的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
复制代码@Slf4j
public abstract class AbstractTrendChartHandler {

private final EducationManager educationManager;

public List<TrendChartDataVo> handle(EducationQuery query){
return getTrendChartData(query);
}

private List<TrendChartDataVo> getTrendChartData(EducationQuery query) {
DateRangeQueryVo dateRangeQueryVo = new DateRangeQueryVo();
dateRangeQueryVo.setStartDate(LocalDate.now().minusWeeks(TrendChartConstant.towMonthsWeeks));
dateRangeQueryVo.setEndDate(LocalDate.now());
List<TrendChartDataVo> trendChartDataVos = educationManager.getTrendChartData(query.getAreaCode(),
AreaRankingType.Companion.toAreaRankingType(query.getAreaRankingType()), dateRangeQueryVo);
return getValuesOperation(trendChartDataVos);
}

/**
* 趋势图的每个点的数值处理(默认是返回与前七天平均值的差值)
*
* @param trendChartDataVos
* @return
*/
protected List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
if (CollectionUtils.isEmpty(trendChartDataVos) || trendChartDataVos.size() < TrendChartConstant.towMonthsWeeks) {
return new ArrayList<>();
}
List<TrendChartDataVo> newTrendChartDataVos = new ArrayList<>();
IntStream.range(0, trendChartDataVos.size()).forEach(i -> {
if (i == 0){
return;
}
TrendChartDataVo trendChartDataVo = new TrendChartDataVo();
trendChartDataVo.setDate(trendChartDataVos.get(i).getDate());
trendChartDataVo.setValue(trendChartDataVos.get(i).getValue() - trendChartDataVos.get(i - 1).getValue());
newTrendChartDataVos.add(trendChartDataVo);
});
return newTrendChartDataVos;
}

@Autowired
public AbstractTrendChartHandler(EducationManager educationManager) {
this.educationManager = educationManager;
}
}

可以看到,handle(EducationQuery query)方法是统一处理方法,子类可以继承也可以默认使用父类方法。getTrendChartData(EducationQuery query)是模板方法,子类一定会执行该方法,并可以重写父类的getValuesOperation(List trendChartDataVos)方法。

三个子类如下:

1
2
3
4
5
6
7
8
9
10
复制代码@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COURSEWARE_COUNT)
public class NewClassesHandler extends AbstractTrendChartHandler {

@Autowired
public NewClassesHandler(EducationManager educationManager) {
super(educationManager);
}
}
1
2
3
4
5
6
7
8
9
10
复制代码@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COMMENT_COUNT)
public class NewCommentsHandler extends AbstractTrendChartHandler {

@Autowired
public NewCommentsHandler(EducationManager educationManager) {
super(educationManager);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.SCHOOL_INDEX)
public class PigeonsIndexHandler extends AbstractTrendChartHandler {

public List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
if (trendChartDataVos.size() <= TrendChartConstant.towMonthsWeeks) {
return new ArrayList<>();
}
trendChartDataVos.remove(0);
return trendChartDataVos;
}

@Autowired
public PigeonsIndexHandler(EducationManager educationManager) {
super(educationManager);
}
}

可以看到,子类NewClassesHandler和NewCommentsHandler类都默认使用了父类的模板,实现了这两种趋势图的逻辑。而PigeonsIndexHandler类则重写了父类的模板中的方法,使用了父类的逻辑后使用子类中重写的逻辑。

以上是策略规则,接下来是策略获取类 TrendChartHandlerContext类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码@SuppressWarnings("unchecked")
public class TrendChartHandlerContext {

private Map<Integer, Class> handlerMap;

TrendChartHandlerContext(Map<Integer, Class> handlerMap) {
this.handlerMap = handlerMap;
}

public AbstractTrendChartHandler getInstance(Integer type) {
Class clazz = handlerMap.get(type);
if (clazz == null) {
throw new IllegalArgumentException("not found handler for type: " + type);
}
return (AbstractTrendChartHandler) BeanTool.getBean(clazz);
}
}

该类用于将前端传入的type转为子类对象。

TrendChartHandlerContext中的handlerMap的值则为这三种趋势图的类型的枚举中的值。由

TrendChartHandlerProcessor类统一扫描自定义注解的值,并统一将类型和子类对象放入handlerMap中。

使用策略:

1
2
3
4
5
6
7
8
9
10
复制代码    /**
* 查看指数/新增课件数/新增点评数走势图
*
* @param query
* @return
*/
@GetMapping("/charts")
public Object queryDeviceBrand(@Validated(value = {EducationGroup.GetTrendChart.class}) EducationQuery query) {
return ResultBuilder.create().ok().data(educationService.queryTrendChartData(query)).build();
}

service逻辑实现:

1
2
3
复制代码    public List<TrendChartDataVo> queryTrendChartData(EducationQuery query) {
return trendChartHandlerContext.getInstance(query.getAreaRankingType()).handle(query);
}

可以看到,使用策略时只需要调用策略的handler方法即可,无需关注type,规避掉大量的if else代码。

工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码@Component
public class BeanTool implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (applicationContext == null) {
applicationContext = context;
}
}

public static Object getBean(String name) {
return applicationContext.getBean(name);
}

public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
复制代码public class ClassScaner implements ResourceLoaderAware {

private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();

private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

@SafeVarargs
public static Set<Class<?>> scan(String[] basePackages, Class<? extends Annotation>... annotations) {
ClassScaner cs = new ClassScaner();

if (ArrayUtils.isNotEmpty(annotations)) {
for (Class anno : annotations) {
cs.addIncludeFilter(new AnnotationTypeFilter(anno));
}
}

Set<Class<?>> classes = new HashSet<>();
for (String s : basePackages) {
classes.addAll(cs.doScan(s));
}

return classes;
}

@SafeVarargs
public static Set<Class<?>> scan(String basePackages, Class<? extends Annotation>... annotations) {
return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils
.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(
resourceLoader);
}

public final ResourceLoader getResourceLoader() {
return this.resourcePatternResolver;
}

public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}

public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(0, excludeFilter);
}

public void resetFilters(boolean useDefaultFilters) {
this.includeFilters.clear();
this.excludeFilters.clear();
}

public Set<Class<?>> doScan(String basePackage) {
Set<Class<?>> classes = new HashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ org.springframework.util.ClassUtils
.convertClassNameToResourcePath(SystemPropertyUtils
.resolvePlaceholders(basePackage))
+ "/**/*.class";
Resource[] resources = this.resourcePatternResolver
.getResources(packageSearchPath);

for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader
.getClassMetadata().getClassName()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"I/O failure during classpath scanning", ex);
}
return classes;
}

protected boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}

这样就算解决类if else的问题了,但是大家会发现有大量的工具需要引入,增加了许多代码量,看上去会不够美观。

模块优化Round 2:

未完待续

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

第六节:Activiti60——TaskService相关

发表于 2019-11-26

一、概述

介绍:在activiti的任务中,与任务相关的任务为以下三种,其含义如下:

  • 候选人(组):代表有权限对任务进行操作的人。
  • 持有人:指当前任务的执行人,他此时持有该任务。
  • 代理人:当一个人持有人不方便处理任务时,可以将任务指给另一人,被指的人即为代理人。

二、候选人

介绍:任务和候选人的关系是一个中间表(act_ru_identitylink)来保存的,所有任务和候选人的关系是多对多。

  1. 编码测试如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
复制代码ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

TaskService taskService = processEngine.getTaskService();

//此处新建一个任务。该处新建在生产上不能这样用,因为任务是要绑定到流程上的。此处只是演示,直接new了一个任务
String taskId = UUID.randomUUID().toString();
Task task = taskService.newTask(taskId);
task.setName("测试任务");
taskService.saveTask(task);

//创建一个用户,因为候选用户组不支持由用户组查询任务,所有此处设置任务的候选人。
IdentityService identityService = processEngine.getIdentityService();

String userId = UUID.randomUUID().toString();
User user = identityService.newUser(userId);
user.setFirstName("老");
user.setLastName("王");
identityService.saveUser(user);

//给任务设置候选人
taskService.addCandidateUser(taskId,userId);

//反向查询任务:根据用户来查询他拥有的任务
List<Task> list = taskService.createTaskQuery().taskCandidateUser(userId).list();
System.out.println(user.getFirstName() + "" + user.getLastName() + " 用户有权限处理的任务有:");
list.forEach(task1 -> System.out.println(task1.getName()));


//关闭
processEngine.close();
System.exit(0);
  1. 结果查看
    • 用户表查看:
      用户
    • 任务表查看:
      任务
    • 关系表:
      关系
    • 运行结果:
      运行结果

三、持有人

介绍:一个人可以持有多个任务,一个任务只能有一个持有人,因此持有人和任务的关系是一对多。持有人关系表现在任务表act_ru_task的OWNER字段中。

  1. 编码测试:
1
2
3
4
5
6
复制代码//其他代码和上个候选人内容一样
//给任务设置持有人
taskService.setOwner(taskId,userId);

//反向查询任务:根据持有人查询任务
List<Task> list = taskService.createTaskQuery().taskOwner(userId).list();
  1. 结果
    在这里插入图片描述

四、代理人

介绍:一个人可以是多个任务的代理人,一个任务只能有一个代理人,因此代理人和任务的关系是一对多

区别:taskService.setAssignee()和taskService.claim()都可以给任务设置代理人,区别:

  • setAssignee方法可以随时任意为任务指定代理人(可多次调用),
  • 而claim方法给任务指定一次代理人后,如果再次调用claim来指定代理人,则会抛出异常:ActivitiTaskAlreadyClaimedException: Task ‘b7ec485a-a4cd-4bdc-8117-8beb76a81c2f’ is already claimed by someone else.
  1. 编码测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码//用户一
String userId = UUID.randomUUID().toString();
User user = identityService.newUser(userId);
user.setFirstName("小");
user.setLastName("明");
identityService.saveUser(user);

//用户二
String userId2 = UUID.randomUUID().toString();
User user2 = identityService.newUser(userId2);
user2.setFirstName("大");
user2.setLastName("明");
identityService.saveUser(user2);

//1.使用taskService.setAssignee()方法给任务设置代理人
// taskService.setAssignee(taskId,userId);
// taskService.setAssignee(taskId,userId2);

//2.使用taskService.claim()方法给任务设置代理人
taskService.claim(taskId,userId);
taskService.claim(taskId,userId2);

//完成任务
//taskService.complete(taskId);
  1. 数据库查看:
    代理人
  2. 调用两次claim的报错如下:
    报错

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

《吐血整理》顶级程序员工具集

发表于 2019-11-26

你知道的越多,你不知道的越多

点赞再看,养成习惯

GitHub上已经开源 github.com/JavaFamily 有一线大厂面试点脑图、个人联系方式和人才交流群,欢迎Star和指教

前言

这期是被人才群交流里,还有很多之前网友评论强行顶出来的一期,就是让我介绍自己常用的一些工具给他们安利一下,我一听很高兴呀,帅丙我这么乐于奉献的人是吧。

主要是能水一篇文章就很开心,不过写下来发现花的时间完全不比写技术少,点赞!!!

千万不要白嫖,真香警告⚠️。

但是我在构思这篇文章的时候发现我贴个标题,然后发下软件信息会不会太乏味了,于是创作鬼才我呀,准备用一个产品的研发流程,是的就是用这样的一个思路去写这个工具集的介绍文章。

因为读者很多还是学生,还有很多应届生,对一个需求的研发流程都不是很熟悉,还有可能对于以后自己需要使用到的工具都不是很熟悉,那我就一一罗列一下,帅丙我作为一个还算有点小经验的程序员都使用哪些工具呢?

那下面就跟随暖男的脚步,走进顶级程序员的百宝箱吧(我所有的标题都是噱头就为了夸大其词,我是低级程序员,大家看了也不能吊打面试官,笑笑就好了)。

注意:下面的软件我都是简单的介绍下是干啥的,因为太多了,真正的功能需要大家深挖的,能力允许的朋友下载正版,破解方法去Github github.com/JavaFamily 加我回复【软件】就好了。

正文

既然都说了帅丙是要用一个产品的研发流程角度去介绍软件,那我们先看看一个产品有哪些研发流程,帅丙就用自己接触的阿里系的研发流程举例了,这也基本上是互联网大厂的研发流程了,可能细节有出入,但是绝对大同小异。

Tip:我从开发的视角去介绍的话我就会跳过一些软件,比如提出需求这个其实一般都是文档类的,wiki呀这样的形式。

我就不介绍那种流程工具了,公司很多自研的,大家工作了再去熟悉也很快的。

概要设计:

概要设计,这个是大厂程序员需求下来之后基本上都会做的一步,不过看需求大小,可能很多小需求直接就详细设计了。

很多不了解的同学可能会问,需要设计什么呢?为什么要设计呢?

问得好,经常看我文章的都知道,技术是把双刃剑,你用了技术之后你是不是需要列出他的优点缺点,出问题之后的解决方案,还有可能出现的问题,注意点等等。

这么是为了让你能有把控力,比如你用了个Es(Elasticsearch)你什么都不管你就是要接入它,你把他开发好了上线了,但是有啥坑你知道么?上线崩了怎么办?

帅丙我做Es设计的时候设计被老大打回N次,不过成长真心大,对Es的了解也飞速进步。

其实远远不止这些问题,这就是我们做设计的主要原因,也是大家工作里面能成长的途径之一,你以为大佬们的经验是怎么来的?

工具:Xmind/ProcessOn
  • Xmind官网地址: www.xmind.cn
  • ProcessOn在线作图地址:www.processon.com

我这里列举了两个工具,他们两个都可以做概要设计的脑图,我给大家看看我设计过的一些脑图,都是出自这两个软件。

大家在学习,看书等等的时候做个脑图,我告诉你后面你复习还是干啥都思路贼清晰,而且效率瞬间高很多。

详细设计

小伙伴又要问了啥是详细设计呀帅丙?

诶呀简单,见名知意嘛,概要设计是大概的设计,详细设计是详细的设计。

我们研发的时候整个流程往往很复杂,如果你理解不对直接就写代码,最后容易造成返工,延期,加班,被骂,心情差,回家吵架,离家出走。。。

看到不做详细设计的后果了吧,其实大家花点时间做详细设计很有必要,你思路完全清晰了,写代码那就是分分钟的事情,不是嘛?

那再看看帅丙的一个小设计吧,之前文章中大量的流程图,时序图都来自它,主要是这玩意还是在线的,都不用下载很方便啊。

详细设计的工具我用的就是在线作图神器:ProcessOn

  • 在线画图地址:www.processon.com

总之一句话很香,流程图、思维导图、原型图、UML、网络拓扑图、组织结构图、BPMN等等一应俱全

Ascilflow

  • 官网地址: asciiflow.com

进入网站后直接开画!网页上端是菜单,自行操作!

研发

这个关键了:工欲善其事,必先利其器

想必大家都知道这个谚语吧,我就说一下我写代码要用到的软件吧。

Intellij IDEA

  • 官网地址 : www.jetbrains.com/idea

这个我想都不用我介绍了吧,Java的同学都耳熟能详了,有同学问为啥不用eclipse呀,我不作回答,但是我只能告诉你IDEA肯定是效率还有很多方面都要香的。

不过看个人习惯的哈,新同学还是推荐IDEA!!!

WebStorm

  • 官网地址 : www.jetbrains.com/webstorm

有知道同学要问了,帅丙这不是前端的开发工具么,为啥你要用,帅丙我呀全才来的呀,前端偶尔也写点的嘛,主要是js,vue,jq,还有丢丢React,不过最近帮三歪改前端居然是jsx有没有大佬救救我啊。

Visual Studio Code

  • 官网地址:code.visualstudio.com

Visual Studio Code是一个轻量且强大的跨平台开源代码编辑器(IDE),支持Windows,OS X和Linux。内置JavaScript、TypeScript和Node.js支持,而且拥有丰富的插件生态系统,可通过安装插件来支持C++、C#、Python、PHP等其他语言。

PyCharm

  • 官网地址 :www.jetbrains.com/pycharm

这个是python之前那次还记得我写代码大赛那期嘛,最开始就是用这个写的,平时学的时候也用用。

Navicat Premium

  • 官网地址 :www.navicat.com.cn

这个呀是数据库的可视化工具很香很好用,不过我们线上的表都是网页操作的,这个只能用来看看本地的开发表了,不过也是很有必要的,你SQL怎么都得本地跑一下没问题,才向DBA申请的嘛。

Postman

  • 官网地址 :www.getpostman.com

这个是接口调试的神器,单测比较繁琐的你可以试试这个嘛,不过很多接口还是只能写写单测,反正很香。

不过这个名字一看就是男生用的,那我们女生用啥呢?往下看

Postwoman

  • 官网地址 :postwoman.io

PostMan一听就是男生用的,咋妹子们肯定要用最近开源的Postwoman啊,它是一款开源的 Postman 替代品

开源没多久优点如下:

  • 轻盈,可直接在线访问;
  • 简约,采用简约的 UI 设计精心打造;
  • 支持 GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH 方法;
  • 支持验证;
  • 实时,发送请求即可获取响应。

GIt

  • 官网地址 :git-scm.com

代码文档管理工具,版本控制工具,大家之后的代码基本上都是使用git做版本管理

Maven

  • 官网地址 :maven.apache.org

目前帅丙用到Maven的主要功能是:项目构建、项目构建、项目依赖管理、软件项目持续集成、版本管理、项目的站点描述信息管理

Gradle

  • 官网地址 :gradle.org

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。

它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置。

这个跟Maven功能是一样的,但是两者语法不一样,而且我觉得版本控制的时候没Maven好使,有小坑,胜在语法简洁。

Charles

  • 官网地址 :www.charlesproxy.com

这玩意是个HTTP代理服务器,我用它的主要用法就是抓包!

都知道我是做电商的嘛,很多线上接口线上问题怎么排查呢,抓包看看数据呗。

JMeter

  • 官网地址 :jmeter.apache.org

Apache JMeter是Apache组织开发的基于Java的压力测试工具

是的就是用来压测的,你怎么模拟很多请求呀,就用它就对了。

Dash

  • 官网地址 :kapeli.com/dash

Dash for mac是使用与Mac OS平台的软件编程文档管理工具,可以浏览API文档,以及管理代码片段工具。Dash自带了丰富的API文档,涉及各种主流的编程语言和框架。

Devdocs

  • 官网地址:devdocs.io

上面那个的兄弟,但是这个不用下载,在线的

DataGrip

  • 官网地址 :www.jetbrains.com/datagrip

DataGrip是JetBrains公司推出的管理数据库的产品,对于JetBrains公司,开发者肯定都不陌生,IDEA和ReSharper都是这个公司的产品,用户体验非常不错。

最开始我用它就单纯看同事在用很酷的界面,后面发现功能也香,高亮文本啥的,很多功能大家可以去挖。

JVisualVM

  • 官网地址 :visualvm.github.io

VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

VisualVM可以根据需要安装不同的插件,每个插件的关注点都不同,有的主要监控GC,有的主要监控内存,有的监控线程等。

Iterm2

  • 官网地址 :https://www.iterm2.com/

这个是我做日志排查的客户端工具,也是支持很多配置,直接ssh上跳板机查看线上机器的状态等等,线上问题排查还是很有必要的。

网*有道词典

  • 官网地址 :www.youdao.com

有朋友要疑问了帅丙这是什么情况,鬼畜乱入么?

不是的,我们研发的时候很多单词不认识,或者命名的时候这个词汇的英文不知道还是很有必要查一下的,规范的命名是你成为顶级大牛的第一步,你命名都是A,B,C你代码review的时候,你的Leader会叫hr让你提交前回家过年的,马上过年了大家不要轻易尝试。

Sublime Text

  • 官网地址 :www.sublimetext.com

这是个文本记录的工具,也可以用于写代码,但是我们有IDE了那就用来当日常琐事记录的工具,临时文档处理的工具也是可以的,反正还是很好用的。

刚好最近呀人才群里的人才们都叫我安利一下我做笔记还有写博客的工具,说我排版好看。(我飘了)

安排!

印象笔记

  • 官网地址 :www.yinxiang.com

这个可以说是陪伴我大学到现在的一个工具了吧,我数了下,上千文件了,我的生活琐事的记录,还有学编程之后的很多东西,我都是收录在里面的。

这个东西我就不和别的笔记比较了,因为我最开始就用的这个一直没换过,好不好用我说了不算的,大家下一个看看就知道了。

Typora

  • 官网地址 :www.typora.io

Typora是我一直写Markdown的工具,好用到不行,还可以切换模式你敢信?打字机模式,专注模式,源码模式总有一个你的菜.

Ipic

  • 官网地址 :ipic.ca

图床我是配合Typora一起使用的大家Markdown是不是复制图片进来都是本地的地址,发到网上就会失效的,但是跟Typora搞基的这个工具他可以在你复制进来的时候直接传到网上了,你再去任何平台发表都可以随心所欲了。

Md2All

  • 官网地址 :md.aclickall.com

大家好奇我的markdown怎么这么好看呀,其实我写了markdown之后还转成了html的,用的也就是上面这个工具。

我写完也是在这里面进行排版然后发出去的,排版要花好久,忍不住给帅丙点赞。

图像处理

Adobe Photoshop CC 2019

  • 官网地址 :www.adobe.com/cn/products…

Ps嘛大家都知道的,正常的图片处理啊用这个香,没事帮美女P下图,搞不好能找个女朋友(我又开始YY了)

Adobe Premiere Pro CC 2019

  • 官网地址 :www.adobe.com/products/pr…

这个可能是大家使用得最多的视频处理软件了吧,很好用!!!

功能全到无法令人呼吸,缺点就是学习成本有点大,入门很快,想成为大神需要大量时间积累。

Adobe After Effects CC 2019

  • 官网地址 :www.adobe.com/cn/products…

不知道大家视频看得多么,很多视频的特效开场都是这个做的,下面也有一个我的demo。

GIPHY CAPTURE

  • 官网地址 :giphy.com/apps/giphyc…

有时候大家文章不想用静态的图画去表达,想录制写代码的Gif动图,那这个软件是真的好使。

视频播放 KMPlayer

  • 官网地址 :www.kmplayer.com

其实帅丙我心中有个播放神器的,快播。

可惜了还没用多久,播播就夭折了,我会使用下面这个播放软件去播放我自己的视频。

因为很多格式电脑自带的可能不支持,我又喜欢剪辑视频,所以一直用它了。

DouTu

  • 官网地址 : www.52doutu.cn/maker/1/?or…

大家是不是发现我之前的文章很多表情包,其实都是在线制作的。

Carbon

  • 官网地址 :carbon.now.sh/

帅丙之前的文章里面很多代码的图片都是这个网站生成的,很多样式可以选择,就很好看。

CodeLF

  • 官网地址 :unbug.github.io/codelf

这个网站有意思了,写代码不知道单词怎么命名就去这里查,他是GItHub的一个爬虫工具吧,看看大神的命名总是会有思路的吧。

注意:上面的软件我都是简单的介绍下是干啥的,因为太多了,真正的功能需要大家深挖的,能力允许的朋友下载正版,破解方法去Github github.com/JavaFamily 加我回复【软件】就好了。

总结

其实比较香的工具还有太多了,我这里就不一一介绍了,其实有个很简单的办法,就是问,学习,借鉴。

我就是这样的,我看Leader用的什么工具我就问他这是啥,好用不?怎么用?

包括我写博客吧,其实才写了20多天,第一天准备写的时候我就直接问三歪(Java3y),你用的什么开发工具呀,怎么用的呀,包括他主要发布的哪些平台呀,发布的时间段呀,我都直接问。

他坐我对面,迫于我的淫威不得不和盘托出,主要是最近有个需求需要我帮他写代码哈哈。

别人都这么多的经验下来了,软件能差嘛?而且使用过程中不会的还可以问一下对方,不香嘛。

絮叨

先看人才交流群的某个人才提的问题:

Tip: GItHub github.com/JavaFamily 上有进群方式和个人联系方式,说实话在这个群,哪怕您不说话,光看聊天记录,都能学到东西(美团王炸,三歪(Java3y),并夕夕等的大佬都在)。

不知道大家是否还记得我之前写的幂等这样的情况?

就是下图这样的情况,我下单增减GMV先去看看这个流水有么,有就证明加过了,就直接返回,没有才继续下面的流程。

他其实提的问题很好,因为我们日常开发都是主从同步,读写分离的,就是说我们可能加GMV加了但是我们操作的是主库,他需要将数据同步到从库,但是这个过程中他发生了延迟。

那这个时候如果别的系统这个订单号消息重试了,你是不是又进来了,你去查询流水你发现没流水你以为没加,你执行加钱的操作,结果那条延迟的也好了,是不是就加了两次。

正常开发中确实会有,但是主从延迟这个应该让DBA(数据库管理员 Database Administrator)去考虑的,但是呢我说过不能写有逻辑漏洞的代码嘛,其实很简单,把他放Redis嘛,设置一个30分钟左右的时间,这期间的重复消费都可以避免,要是延迟超过30分钟了那其实问题已经很大了,DBA会知道的。

这篇是吐血整理,大家好好食用,记得点个赞!👍

点关注,不迷路

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。

我后面会每周都更新几篇一线互联网大厂面试和常用技术栈相关的文章,非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「敖丙」我有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!

白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

敖丙 | 文 【原创】

如果本篇博客有任何错误,请批评指教,不胜感激 !


文章每周持续更新,可以微信搜索「 三太子敖丙 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub github.com/JavaFamily 已经收录,有一线大厂面试点思维导图,也整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

微服务高可用利器——Hystrix熔断降级原理&实践总结 前

发表于 2019-11-25

head

前言

最近在工作中参与组内服务稳定性建设,梳理我们目前服务现状并接入公司自研稳定性保障平台。对公司内自研组件以及业界流行的Hystrix做了学习,Netflix Hystrix 里面大量RxJava响应式实现,实在看着有点绕。所以在这里梳理一些实践以及Hystrix知识点。

为什么要做这个事情

服务的稳定是公司可持续发展的重要基石,随着业务量的快速发展,一些平时正常运行的服务,会出现各种突发状况,而且在分布式系统中,每个服务本身又存在很多不可控的因素,比如线程池处理缓慢,导致请求超时,资源不足,导致请求被拒绝,又甚至直接服务不可用、宕机、数据库挂了、缓存挂了、消息系统挂了…

对于一些非核心服务,如果出现大量的异常,可以通过技术手段,对服务进行降级并提供有损服务,保证服务的柔性可用,避免引起雪崩效应。

例如:一个依赖30个服务的系统,每个服务99.99%可用,99.99%的30次方 ≈ 99.7% ,0.3% 意味着1亿次请求会有 3,000,00次失败 ,换算成时间大约每月有2个小时服务不稳定,随着服务依赖数量的变多,服务总体可用性会变得更差。
假设我们当前服务的外部依赖中,有一个服务出现了故障,可能是网络抖动出现了超时,亦或服务挂掉导致请求超时,短时间内看起来像下图这样:

7430ba47b3e649c4a27fcacd12103453-image.png

慢慢的大量业务线程都会阻塞在对故障服务的调用上,请求排队,服务响应缓慢,系统资源渐渐消耗,最终导致服务崩溃,更可怕的是这种影响会持续的向上传递,进而导致服务雪崩。

5413341075ba4846933350395ec5581d-image.png

如何去做

  • 消除依赖: 梳理去除、隔离。 比如系统尽量减少第三方依赖;核心与非核心业务服务化拆分;服务内各场景线程池级别隔离
  • 弱化依赖: 旁路、缓存。
  • 控制依赖: 熔断降级、服务限流、设置合理的超时重试。 避免级连失败

可用性指标

业界高可用的标准是按照系统宕机时间来衡量的:

首先去梳理各个业务链路的服务依赖关系以及依赖的调用量,识别出哪些服务是强依赖,哪些是弱依赖。
强弱依赖业界定义

感性: 就是当下游依赖服务出现问题时,当前系统会受到一些影响,让用户有感觉的是强依赖,没感觉的是弱依赖。

理性: 不影响核心业务流程,不影响系统可用性的依赖都可以叫做弱依赖,反之就是强依赖。

对于强依赖尽量具备降级服务逻辑,因为毕竟会影响核心链路。对于弱依赖可随时熔断。

设置合理的超时和重试

对外部系统和缓存、消息队列等基础组件的依赖。假设这些被依赖方突然发生了问题,我们系统的响应时间是:内部耗时+依赖方超时时间*重试次数。如果超时时间设置过长、重试过多,系统长时间不返回,可能会导致连接池被打满,系统死掉;如果超时时间设置过短,系统的可用性会降低。

  • 首先要调研被依赖服务自己调用下游的超时时间是多少。调用方的超时时间要大于被依赖方调用下游的时间。
  • 统计这个接口99%的响应时间是多少,设置的超时时间在这个基础上加50%。如果接口依赖第三方,而第三方的波动比较大,也可以按照95%的响应时间。
  • 重试次数如果系统服务重要性高,则按照默认,一般是重试三次。否则,可以不重试。

Hystix

以业界比较流行的熔断降级组件Hystix为例,来学习其基本的工作原理

Hystix工作流程图

3558e27a37a24d92a8530df3eeb52713-image.png

​ 下面将更详细的解析每一个步骤都发生哪些动作:

  • 构建一个HystrixCommand或者HystrixObservableCommand对象。

第一步就是构建一个HystrixCommand或者HystrixObservableCommand对象,该对象将代表你的一个依赖请求,向构造函数中传入请求依赖所需要的参数。

如果构建HystrixCommand中的依赖返回单个响应,例如:

1
复制代码HystrixCommand command = new HystrixCommand(arg1, arg2);

如果依赖需要返回一个Observable来发射响应,就需要通过构建HystrixObservableCommand对象来完 成,例如:

1
复制代码HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
  • 执行命令

有4种方式可以执行一个Hystrix命令。

+ `execute()`—该方法是阻塞的,从依赖请求中接收到单个响应(或者出错时抛出异常)。
+ `queue()`—从依赖请求中返回一个包含单个响应的Future对象。
+ `observe()`—订阅一个从依赖请求中返回的代表响应的Observable对象。
+ `toObservable()`—返回一个Observable对象,只有当你订阅它时,它才会执行Hystrix命令并发射响应。
1
2
3
4
复制代码K             value   = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable

同步调用方法execute()实际上就是调用queue().get()方法,queue()方法的调用的是toObservable().toBlocking().toFuture().也就是说,最终每一个HystrixCommand都是通过Observable来实现的,即使这些命令仅仅是返回一个简单的单个值。

  • 响应是否被缓存

如果这个命令的请求缓存已经开启,并且本次请求的响应已经存在于缓存中,那么就会立即返回一个包含缓存响应的Observable。

  • 回路器是否打开

当命令执行执行时,Hystrix会检查回路器是否被打开。

如果回路器被打开(或者tripped),那么Hystrix就不会再执行命名,而是直接路由到第8步,获取fallback方法,并执行fallback逻辑。

如果回路器关闭,那么将进入第5步,检查是否有足够的容量来执行任务。(其中容量包括线程池的容量,队列的容量等等)。

  • 线程池、队列、信号量是否已满

如果与该命令相关的线程池或者队列已经满了,那么Hystrix就不会再执行命令,而是立即跳到第8步,执行fallback逻辑。

  • 计算回路指标[Circuit Health]

Hystrix会报告成功、失败、拒绝和超时的指标给回路器,回路器包含了一系列的滑动窗口数据,并通过该数据进行统计。

它使用这些统计数据来决定回路器是否应该熔断,如果需要熔断,将在一定的时间内不在请求依赖[短路请求](译者:这一定的时候可以通过配置指定),当再一次检查请求的健康的话会重新关闭回路器。

  • 获取FallBack

当命令执行失败时,Hystrix会尝试执行自定义的Fallback逻辑:

+ 当`construct()`或者`run()`方法执行过程中抛出异常。
+ 当回路器打开,命令的执行进入了熔断状态。
+ 当命令执行的线程池和队列或者信号量已经满容。
+ 命令执行超时。

Hystrix设计原则

1.防止单个服务的故障,耗尽整个系统服务的容器

2.用快速失败代替排队(每个依赖服务维护一个小的线程池或信号量,当线程池满或信号量满,会立即拒绝服务而不会排队等待)和优雅的服务降级;当依赖服务失效后又恢复正常,快速恢复

3.提供接近实时的监控和警报,从而能够快速发现故障和修复。监控信息包括请求成功,失败(客户端抛出的异常),超时和线程拒绝。如果访问依赖服务的错误百分比超过阈值,断路器会跳闸,此时服务会在一段时间内停止对特定服务的所有请求

4.将所有请求依赖服务封装到HystrixCommand或HystrixObservableCommand对象中,然后这些请求在一个独立的线程中执行。使用隔离技术来限制任何一个依赖的失败对系统的影响。每个依赖服务维护一个小的线程池(或信号量),当线程池满或信号量满,会立即拒绝服务而不会排队等待

38d764f8416e4320921193551dbeceac-image.png

Hystrix特性

断路器

下面的图展示了HystrixCommand和HystrixObservableCommand如何与HystrixCircuitBroker进行交互。

7237eed4c0e147e29b8ebd7ce1610acd-image.png

回路器打开和关闭有如下几种情况:

  • 假设回路中的请求满足了一定的阈值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())
  • 假设错误发生的百分比超过了设定的错误发生的阈值HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
  • 回路器状态由CLOSE变换成OPEN
  • 如果回路器打开,所有的请求都会被回路器所熔断。
  • 一定时间之后HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),下一个的请求会被通过(处于半打开状态),如果该请求执行失败,回路器会在睡眠窗口期间返回OPEN,如果请求成功,回路器会被置为关闭状态,重新开启1步骤的逻辑。

下图是熔断自动回复流程图:

4ed767dd368b4b35b179779db390a331-image.png

当出现问题时,Hystrix会检查一个肯定时长(图中为10s)的一个时间窗(window),在这个时间窗内能否有足够多的请求,假如有足够多的请求,能否错误率已经达到阈值,假如达到则启动断路器熔断机制,这时再有请求过来就会直接到fallback路径。在断路器打开之后,会有一个sleep window(图中为5s),每经过一个sleep window,当有请求过来的时候,断路器会放掉一个请求给remote 服务,让它去试探下游服务能否已经恢复,假如成功,断路器会恢复到正常状态,让后续请求重新请求到remote 服务,否则,保持熔断状态。sleep window 实现机制类似于校招常考的那个窗口滑动求最值的问题!

fallback

资源隔离

Hystrix采用舱壁模式来隔离相互之间的依赖关系,并限制对其中任何一个的并发访问。

5e42ff3d68f4476a88ef201cf98284a6-image.png

3392f44f296345c9ae1197d1f924ef09-image.png

  • 线程和线程池

客户端(第三方包、网络调用等)会在单独的线程执行,会与调用的该任务的线程进行隔离,以此来防止调用者调用依赖所消耗的时间过长而阻塞调用者的线程。

[Hystrix uses separate, per-dependency thread pools as a way of constraining any given dependency so latency on the underlying executions will saturate the available threads only in that pool]

Netflix,设计Hystrix,并且选择使用线程和线程池来实现隔离机制,有以下几个原因:

  • 很多应用会调用多个不同的后端服务作为依赖。
  • 每个服务会提供自己的客户端库包。
  • 每个客户端的库包都会不断的处于变更状态。
  • [Client library logic can change to add new network calls]
  • 每个客户端库包都可能包含重试、数据解析、缓存等等其他逻辑。
  • 对用户来说,客户端库往往是“黑盒”的,对于实现细节、网络访问模式。默认配置等都是不透明的。
  • [In several real-world production outages the determination was “oh, something changed and properties should be adjusted” or “the client library changed its behavior.]
  • 即使客户端本身没有改变,服务本身也可能发生变化,这些因素都会影响到服务的性能,从而导致客户端配置失效。
  • 传递依赖可以引入其他客户端库,这些客户端库不是预期的,也许没有正确配置。
  • 大部分的网络访问是同步执行的。
  • 客户端代码中也可能出现失败和延迟,而不仅仅是在网络调用中。

信号量

772f79f5ac94478caa84d23f430d39b1-image.png

类型 优点 不足 适用
线程 支持排队和超时、支持异步调用 线程调用和切换产生额外开销 不受信客户(比如第三方服务稳定性是无法推测的)
信号量 轻量且无额外开销 不支持任务排队和超时,不支持异步 受信客户、高频高速调用服务(网关、cache)

参数配置项

线程池相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
复制代码import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.*;

/*
* 注意: @HystrixCommand 注解方式依赖 AOP, 不支持在同一个类的内部方法之间直接调用, 必须将被调用类作为 bean 注入并调用
*/
public class DemoCircuitBreakerAnnotation {

/**
* 使用 THREAD 模式及线程池参数、通用参数说明
*/
@HystrixCommand(
groupKey = "GroupAnnotation",
commandKey = "HystrixAnnotationThread",
fallbackMethod = "HystrixAnnotationThreadFallback",
/*
* 线程池名, 具有同一线程池名的方法将在同一个线程池中执行
*
* 默认值: 方法的groupKey
*/
threadPoolKey = "GroupAnnotationxThreadPool",
threadPoolProperties = {
/*
* 线程池Core线程数及最大线程数
*
* 默认值: 10
*/
@HystrixProperty(name = CORE_SIZE, value = "10"),
/*
* 线程池线程 KeepAliveTime 单位: 分钟
*
* 默认值: 1
*/
@HystrixProperty(name = KEEP_ALIVE_TIME_MINUTES, value = "1"),
/*
* 线程池最大队列长度
*
* 默认值: -1, 此时使用 SynchronousQueue
*/
@HystrixProperty(name = MAX_QUEUE_SIZE, value = "100"),
/*
* 达到这个队列长度后, 线程池开始拒绝后续任务
*
* 默认值: 5, MaxQueueSize > 0 时有效
*/
@HystrixProperty(name = QUEUE_SIZE_REJECTION_THRESHOLD, value = "90"),
},
commandProperties = {
/*
* 以 THREAD (线程池)模式执行, run 方法将被一个线程池中的线程执行
*
* 注意: 由于有额外的线程调度开销, THREAD 模式的性能不如 NONE 和 SEMAPHORE 模式, 但隔离性比较好
*
* 默认值: THREAD
*/
@HystrixProperty(name = EXECUTION_ISOLATION_STRATEGY, value = "THREAD"),
/*
* 方法执行超时后是否中断执行线程
*
* 默认值: true, THREAD 模式下有效
*/
@HystrixProperty(name = EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, value = "true"),
/*
* 超时时间参数
* 在 THREAD 模式下, 方法超时后 Hystrix 默认会中断原方法的执行线程, 并标记这次方法的执行结果为失败(影响方法的健康值)
* 同时另开一个线程执行 fallback, 最终返回 fallback 的结果
*
* 默认值: 1000
*/
@HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "500")
/*
* 其余参数参考上面的例子, 或者使用默认值
*/
})
public String HystrixAnnotationThread(String param) {
return "Run with " + param;
}
public String HystrixAnnotationThreadFallback(String param, Throwable ex) {
return String.format("Fallback with param: %s, exception: %s", param, ex);
}
}

信号量相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
复制代码import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.*;

/*
* 注意: @HystrixCommand 注解方式依赖 AOP, 不支持在同一个类的内部方法之间直接调用, 必须将被调用类作为 bean 注入并调用
*/
public class DemoCircuitBreakerAnnotation {

/**
* 使用 SEMAPHORE 模式及通用参数说明
*/
@HystrixCommand(
groupKey = "GroupAnnotation",
commandKey = "HystrixAnnotationSemaphore",
fallbackMethod = "HystrixAnnotationSemaphoreFallback",
commandProperties = {
/*
* 以 SEMAPHORE (信号量)模式执行, 原方法将在调用此方法的线程中执行
*
* 如果原方法无需信号量限制, 可以选择使用 NONE 模式
* NONE 模式相比 SEMAPHORE 模式少了信号量获取和判断的步骤, 效率相对较高, 其余执行流程与 SEMAPHORE 模式相同
*
* 默认值: THREAD
*/
@HystrixProperty(name = EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
/*
* 执行 run 方法的信号量上限, 即由于方法执行未完成停留在 run 方法内的线程最大个数
* 执行线程退出 run 方法后释放信号量, 其他线程获取不到信号量无法执行 run 方法
*
* 默认值: 1000, SEMAPHORE 模式下有效
*/
@HystrixProperty(name = EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "100"),
/*
* 执行 fallback 方法的信号量上限
*
* 注意: 所有模式(NONE|SEMAPHORE|THREAD) fallback 的执行都受这个参数影响
*
* 默认值: Integer.MAX_VALUE
*/
@HystrixProperty(name = FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "1000"),
/*
* 超时时间参数
* 在 SEMAPHORE 模式下, 方法超时后 Hystrix 不会中断原方法的执行线程, 只标记这次方法的执行结果为失败(影响方法的健康值)
* 同时另开一个线程执行 fallback, 最终返回 fallback 的结果
*
* 默认值: 1000
*/
@HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "500"),
/*
* 方法各项指标值存活的滑动时间窗口长度, 每经过一个时间窗口长度重置各项指标值, 比如: 方法的健康值
*
* 默认值: 10000
*/
@HystrixProperty(name = METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value = "10000"),
/*
* 滑动时间窗口指标采样的时间分片数, 分片数越高时, 指标汇总更新的频率越高, 指标值的实时度越好, 但同时也占用较多 CPU
* 采样过程: 将一个滑动时间窗口时长根据分片数等分成多个时间分片, 每经过一个时间分片将最新一个时间分片的内积累的统计数据汇总更新到时间窗口内存活的已有指标值中
*
* 注意: 这个值只影响 Hystrix Monitor 上方法指标值的展示刷新频率,不影响熔断状态的判断
*
* 默认值: 10
*/
@HystrixProperty(name = METRICS_ROLLING_STATS_NUM_BUCKETS, value = "10"),
/*
* 健康值采样的间隔, 相当于时间片长度, 每经过一个间隔将这个时间片内积累的统计数据汇总更新到时间窗口内存活的已有健康值中
*
* 健康值主要包括: 方法在滑动时间窗口内的总执行次数、成功执行次数、失败执行次数
*
* 默认值: 500
*/
@HystrixProperty(name = METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS, value = "500"),
/*
* 一个滑动时间窗口内, 方法的执行次数达到这个数量后方法的健康值才会影响方法的熔断状态
*
* 默认值: 20
*/
@HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"),
/*
* 一个采样滑动时间窗口内, 方法的执行失败次数达到这个百分比且达到上面的执行次数要求后, 方法进入熔断状态, 后续请求将执行 fallback 流程
*
* 默认值: 50
*/
@HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
/*
* 熔断状态停留时间, 方法进入熔断状态后需要等待这个时间后才会再次尝试执行原方法重新评估健康值. 再次尝试执行原方法时若请求成功则重置健康值
*
* 默认值: 5000
*/
@HystrixProperty(name = CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000")
})
public String HystrixAnnotationSemaphore(String param) {
return "Run with " + param;
}
public String HystrixAnnotationSemaphoreFallback(String param, Throwable ex) {
return String.format("Fallback with param: %s, exception: %s", param, ex);
}
}

参考

github Hystrix wiki

美团点评智能支付核心交易系统的可用性实践

高可用系统常用利器(一) - 服务降级 Hystrix

Hystrix工作原理(官方文档翻译)
高可用系统常用利器(一) - 服务降级 Hystrix

超全总结 | 阿里电商故障治理和故障演练实践

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

🔥如何写一篇技术博客,谈谈我的看法

发表于 2019-11-25

前言

只有光头才能变强。

文本已收录至我的GitHub精选文章,欢迎Star:github.com/ZhongFuChen…

我一直推崇学技术可以写技术博客去沉淀自己的知识,因为知识点实在是太多太多了,通过自己的博客可以帮助自己快速回顾自己学过的东西。

我最开始的时候也是只记笔记,认为自己能看得懂就好。但如果想验证自己是不是懂了,可以写成技术博客。在写技术博客的过程中肯定会发现:“这个知识点,我好像还有些没搞懂”。

从梳理/编写的过程,自己也会成长不少

有不少的小伙伴曾经问过我:

  • “3y你是怎么记笔记的阿?我一边看视频一边记笔记,顾头不顾腚的”
  • “你写的博客都是怎么思考的阿?我不会写博客”

不会写技术博客/笔记

下面来谈谈我一些观点(个人/主观),每个人都有自己的方法论,我的观点不适用于所有人。

开局一张图,内容全靠编:

一、如何写一篇技术博客

首先,我认为要把自己当做是一个分享者,而读者是一个小白。然后模拟这个场景:如果你要把你学习到的技术分享给小白,你需要怎么去做。

按照我的思路,我可能会这样做:

  • 首先,这项技术是什么你得先告诉他。
  • 接着,为什么要学习这项技术,学习了这项技术有什么好处。有没有以前的技术跟这个较为相似的,为什么我不用以前的,而要学新的。(这一步非常非常重要)
  • 然后,这项技术的核心用法是什么,给出一些小案例,让小白体验到这项技术。
  • 最后,用这项技术可能会出现什么问题,官方有没有提供方案解决,如果没有,可能的解决方案有哪些。

大白话来说,其实就是:

  • 是什么
  • 为什么
  • 怎么做

一般来说,我会把重点放在 为什么 ,因为我一直认为学习一门技术一定得知道:为什么要学。

举个例子,我当初写【消息队列】的思路:

  • 回顾一下什么是队列,Java已经支持各种类型的队列了,跟消息队列有什么不同。
  • 为什么要用消息队列,不用行不行?用消息队列的好处有哪些
  • 用了消息队列可能会带来的问题

如果遇到逻辑可能较为复杂,或者认为读者会看不太懂的时候,可以画图来描述一下,这样整一篇文章看下来就不会太枯燥。

其实吧,我写文章都是按自己学习时的思路来写。如果我在学习时中途某个地方卡住了,我就认为可能读者在学习的时候也会存在同样的问题。所以,我就把我的理解给记录下来,如果有必要就画图来讲解。

我写了两百多篇Java技术博客,有需要的同学可以关注我的GitHub,欢迎一起学习和交流:github.com/ZhongFuChen…

关注我的公众号,看一下我是怎么写技术博客的

二、文章需要有自己的风格

每个人写出来的博客都有自己的风格。

比如说有漫画型的:

比如说有故事型的:

故事型讲解

比如说有骚气型的:

骚气型

比如说有纯干货型的:

纯干货

说了这么多,其实我想说的是:写博客应该要带有属于自己的风格。而不是把网上的资料直接复制粘贴就完事了,这样没有感情,没有灵魂。(当然了,如果网上可能的确有写得很好的,可以这么做,但不可能整一篇都是相同的嘛)

三、关于排版

一篇好的技术文章,它的排版一般不会差。我认为写技术文章有几点可以提高一下阅读体验:

  1. 整篇文章不要90%都是代码,适当减少代码,贴关键的部分就好了。(完整的代码可以上传到GitHub)
  2. 多分段,技术文章可能就相对枯燥,如果将技术的说明挤在一块,可能阅读起来没那么好的体验
  3. 增加图示说明 or 插入相关的图片

程序员写文章一般都用Markdown语法了,使用恰当的话,文章的排版自然也不会太差啦。所以还没用Markdown语法的同学可以赶紧用起来,这玩意特好学,从学到用起来就几分钟。

  • 我当时连简历都是Markdown语法…

四、关于工具

Markdown编辑器我推荐使用Typora,无论是在Mac还是在Windows上都很好用。使用Markdown就肯定要考虑一个问题:图床用哪家?

无论使用哪家图都有可能会挂,自己搞个图床可能会安全一些(相对来说会比较麻烦),我自己是使用掘金的图床的。而我的文章往往会分发到几个博客网站上,像简书/知乎这种会单独将图片上传到他们的服务器上。

所以,从我个人的使用角度而言,并不会担心图床会挂问题。如果你将文章只发送到一个平台中,还是得关注一下图床是否可能失效的问题。

至于你发哪个平台,我曾经写过一篇【程序员可以选择哪些平台写技术博客?】,当时的总结:

  • 如果不嫌弃博客园的风格,可以选择博客园。否则建议选择:掘金/SegmentFault。
  • 只想管理自己写过的文章,选择:GitHub/GitBook
  • 喜欢折腾:Hexo+GitHub或者WordPress

简单的流程图/思维导图/..可以使用ProcessOn就解决掉了。

最后

有的没的扯了一些,希望能够对「有想要写技术博客,但无从下手的同学」一些帮助。

本已收录至我的GitHub精选文章,欢迎Star:github.com/ZhongFuChen…

乐于输出干货的Java技术公众号:Java3y。公众号内有300多篇原创技术文章、海量视频资源、精美脑图,关注即可获取!

转发到朋友圈是对我最大的支持!

非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「三歪」我有点东西的话 求点赞 求关注️ 求分享👥 求留言💬 对暖男我来说真的 非常有用!!!

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

手把手教你,如何在windows系统搭建mysql主从复制的

发表于 2019-11-25

背景

最近在学习用Spring Aop来实现数据库读写分离的功能。
在编写代码之前,首先是要部署好mysql的环境,因为要实现读写分离,所以至少需要部署两个mysql实例,一主一从,并且主从实例之间能够自动同步,因为我的本机内存并不高,所以就打算在windows上直接搭建mysql的主从实例(不想开虚拟机),但这个过程中却遇到了一些麻烦,虽然最后都解决了,但也花费了不少的时间。为了避免以后在同样的事情上浪费时间,同时也方便读者们能复制相同的场景,所以就写下这篇博客来记录一下搭建环境的过程。

环境说明

本机地址:127.0.0.1(localhost)

mysql版本:mysql-5.7.28-winx64

主库服务名:master,端口3307

从库服务名:slave,端口3308

安装并配置主库master

下载

首先是下载mysql,直接到官网下载zip版的安装包,这里建议下载比较新的版本,比如笔者的版本是5.7,这也是网上很多大神的建议,

解压并创建my.ini文件

解压安装包,命名文件夹为master,进入文件夹,创建一个名为my.ini的空文本,

文本中的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码[client]
# 端口号,默认是3306,同一个环境下不同的mysql实例端口号不能相同
port=3307
default-character-set=utf8

[mysqld]
#主库配置
server_id=1
log_bin=master-bin
log_bin-index=master-bin.index

# 设置为自己MYSQL的安装目录
basedir=D:/software/mysql/master
# 设置为MYSQL的数据目录,data文件夹由mysql自动生成
datadir=D:/software/mysql/master/data
port=3307
character_set_server=utf8
sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER

# 开启查询缓存
explicit_defaults_for_timestamp=true

ps:配置内容中的目录地址要用斜杠,不能用反斜杠,否则等下安装服务会报错找不到目录。

安装master库的服务

1、cmd管理员身份运行,进入master的bin目录下,

2、执行初始化的命令:

1
复制代码mysqld --initialize --user=mysql --console

如果出现类似下面的提升就显示初始化成功,初始化成功会系统自动生成data文件夹,并生成初始密码,

3、初始化完成后,执行安装服务的命令:

1
复制代码mysqld --install master --defaults-file="D:\software\mysql\master\my.ini"

master为服务的名称,–defaults-file为ini文件的路径,出现”Service successfully installed.”即为成功

注:如果安装失败的话,看看是否没有管理员身份运行cmd。
正常来说,安装完master的服务后我们就可以启动mysql了,然而这里有个坑,就是直接mysql后会报错,因为我们还少配置了一个地方,就是注册表中master服务的信息。

所以在未修改注册表之前,建议先别执行启动命令。
4、修改注册表

按win+R,在弹框中输入regedit打开注册表,找到master服务,路径是HKEY_LOCAL_MACHINE–>SYSTEM–>CurrentControlSet–>Services–>master,修改ImagePath为

1
复制代码D:\software\mysql\master\bin\mysqld --defaults-file=D:\software\mysql\master\my.ini master

路径就对应自己安装的master数据库文件夹即可。

5、启动服务

依然是在bin目录下执行启动服务,命令是net master start,启动成功后会出现如下的提示:

6、进入mysql
进入mysql的命令是:mysql -u root -p ,但因为我们改了端口号,所以命令要改为

mysql -u root -P3307 -p,然后输入刚才生成的初始化密码即可进入mysql,

由于这个初始密码实在是太蛋疼,所以我们最好更改密码,更改语句为:

1
复制代码set password=password('新密码');

例如LZ设置的密码为123456,

这样就成功设置了root用户的密码了,然后我们可以用Navicat等图形化工具来操作连接数据库了,

安装从库slave

从库的安装步骤和主库一样,只是my.ini中修改对应的配置即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码[client]
port=3308
default-character-set=utf8

[mysqld]
#从库配置
server_id=2
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin

# 设置为自己MYSQL的安装目录
basedir=D:/software/mysql/slave
# 设置为MYSQL的数据目录
datadir=D:/software/mysql/slave/data
port=3308
character_set_server=utf8
sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER
#开启查询缓存
explicit_defaults_for_timestamp=true

主从库实现关联

两个库都安装好了,我们就可以来操作主从库之间的关联,实现主从之间的复制,

先登录主库,输入 show master status;

可以发现主库中生成了一个二进制的文件,这是一个日志文件,从库关联它就可以实现和主库的同步了。
登录从库,执行下面的命令,

1
复制代码change master to master_host='127.0.0.1',master_port=3307,master_user='root',master_password='123456',master_log_file='master-bin.000001',master_log_pos=0;

不难看出上面都是主库的配置信息,成功后执行命令start slave 开启主从复制

完成之后,我们简单验证下,在master库中创建一个数据库,名字就叫test,

然后slave中也生成了同样的数据库test,

这样,主从复制就成功实现了。
值得说明的是,因为两个库只是做了单向关联,如果往从库中写数据的话,主库是无法同步的。所以从库只能用于读取数据,而主库既能写,也能读,当然,多数情况都是用于写数据,读取数据一般都是从库获取,这样能有效减轻主库的压力,也就是我们常说的读写分离。

如果您觉得阅读本文对您有帮助,还请您帮忙点个赞,这是对我最好的肯定!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…846847848…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%