Git Product home page Git Product logo

huifer / javabook-src Goto Github PK

View Code? Open in Web Editor NEW
50.0 2.0 19.0 40.37 MB

Java相关框架, java 基础, Spring, mybsatis ,SpringBoot ,分布式,微服务

Home Page: https://huifer.github.io/javaBook-src/

License: Apache License 2.0

Java 96.62% Roff 0.83% HTML 0.39% TSQL 0.11% RobotFramework 0.01% CSS 0.17% PLpgSQL 0.11% Shell 0.05% JavaScript 0.88% Vue 0.69% SCSS 0.14%
java java8 dubbo spring springcloud mybatis hibernate spring-mvc sprinbboot2 netty

javabook-src's Introduction

javabook-src's People

Contributors

dependabot[bot] avatar huifer avatar imgbotapp avatar lizongxue-f avatar zeofura avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

javabook-src's Issues

Spring 注解集合

spring 注解

核心注解

@required

  • 在Bean的set方法上使用,表示该属性不可为空

@Autowired

  • 自动注入Bean,方式为ByType。使用@Autowired(required = false)时,即便找不到Bean也不会报错

@qualifier

  • 该注解通常和@Autowired一起使用,可以当作ByName注入Bean,使用@Qualifier可以避免相同类型不同作用Bean注入时的混乱

@configuration

  • 等价于spring.xml配置文件,该注解可以替换为JavaBean方式进行开发,如需使用xml方式开发可以使用@ImportResource(locations={"classpath:applicationContext.xml"})方式.

@componentscan

  • 指定bean扫描路径,和@Configuration一起使用来发现和装配Bean.如果不填写basePackages会从该注解所在包以及子包开始扫描。

@ImportResource

  • 用来加载xml配置文件

@bean

  • 等价于·`,将注解放在方法上交给spring管理

@lazy

  • 延时加载.与@Configuration一起使用所有的Bean都是懒加载.

@value

  • 用于注入application.ymlapplication.properties中配置的属性值

@import

  • 导入其他配置类

语义注解

@component

  • 类级别,表示这是一个spring组件.

@controller

  • 类级别,定义controller,修饰url请求层组件。其本质也是@Commponent

@RestController

  • 类级别,定义controller,修饰url请求层组件。其本质也是@Commponent

@service

  • 类级别,定义service,修饰service层组件.其本质也是@Commponent

@repository

  • 类级别,定义repository,修饰dao层组件。其本质也是@Commponent

MVC注解

@controller

  • controller层组件

@RestController

  • controller层组件

@RequestMapping

  • 表示url请求地址
    • GetMapping:@RequestMapping(method = RequestMethod.GET)
    • PostMapping:@RequestMapping(method = RequestMethod.POST)
    • PutMapping:@RequestMapping(method = RequestMethod.Put)
    • PatchMapping:@RequestMapping(method = RequestMethod.PATCH)
    • DeleteMapping:@RequestMapping(method = RequestMethod.DELETE)

@CookieValue

  • 用在方法上获取cookie

@crossorigin

  • 跨域实现

@ExceptionHandler

  • 用在方法上,指定具体异常处理类

@InitBinder

  • 初始化数据绑定器,用于数据绑定,数据转换

@Valid

  • 数据验证

@MatrixVariable

  • 矩阵变量

@PathVariable

  • 路径变量

@RequestAttribute

  • 绑定请求属性到handler方法参数

@requestbody

  • 指示方法参数应该绑定到Http请求Body HttpMessageConveter负责将HTTP请求消息转为对象

@RequestHeader

  • 映射控制器参数到请求头的值

@RequestParam

  • 请求参数

@responsebody

  • 返回值对象注解转换成json

@ResponseStatus

  • 返回状态码

@SessionAttribute

  • 用于方法参数。绑定方法参数到会话属性

@SessionAttributes

  • 用于将会话属性用Bean封装

@ModelAttribute

定时任务注解

@scheduled

  • 方法级别,参数为 cron 表达式

调度注解

@async

  • 方法级别,每个方法均都在单独的线程中,可接受参数;可返回值,也可不返回值。

测试注解

@BootstrapWith

  • 类级别,配置spring测试上下文

@ContextConfiguration

  • 类级别,指定配置文件

@WebAppConfiguration

  • 类级别,指定测试环境 WebAppConfiguration

@timed

  • 方法级别,指定测试方法的执行时间,超时失败

@repeat

  • 方法级别,指定运行次数

@Commit

  • 类级别&方法级别,指定事务提交

@Rollback

  • 类级别&方法级别,指定事务的回滚

@DirtiesContext

@BeforeTransaction

  • 方法级别,指示具有该注解的方法应该在所有@Transactional注解的方法之前执行

@AfterTransaction

  • 方法级别,指示具有该注解的方法应该在所有@Transactional注解的方法之后执行

@Sql

  • 类级别&方法级别,运行Sql脚本 方法上的@Sql会覆盖类级别的@Sql

@SpringBootTest

  • spring boot 集成测试上下文

@DataJpaTest

@DataMongoTest

  • 内置MongoDB集成测试

@WebMVCTest

  • controller层测试

@AutoConfigureMockMVC

  • controller层测试,加载springboot完整上下文

@MockBean

  • 注入MockBean

@JsonTest

@TestPropertySource

JPA注解

@Entity

  • 实体类注解,常和@Table配合使用

@Table

  • 表注解,name填写数据库表名

@Column

  • 数据表列注解

@Id

  • 主键注解

@GeneratedValue

  • 主键生成策略
    • TABLE:使用一个特定的数据库表格来保存主键。
    • SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
    • IDENTITY:主键由数据库自动生成(主要是自动增长型)
    • AUTO:主键由程序控制

@SequenceGeneretor

  • 自动为实体的数字标识字段/属性分配值

@MappedSuperClass

  • 用在确定是父类的Entity上。父类的属性可被子类继承

@NoRepositoryBean

  • 在充当父类的Repository上注解,告诉Spring不要实例化该Repository

@Transient

  • 表示该属性并非是一个到数据库表字段的映射,ORM框架应忽略它。 如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic

@Basic

  • 指定实体数据属性的加载方式
    • LAZY:延迟加载
    • EAGER:全部加载

@JsonIgnore

  • 指示当进行序列化或反序列化时,忽略该属性

@JoinColumn

  • 一对一:本表中指向另一个表的外键。 一对多:另一个表指向本表的外键

@OneToOne

  • 一对一注解

@OneToMany

  • 一对多注解

@ManyToOne

  • 多对一注解

事务注解

@Transactional

  • 用于接口、接口中的方法、类、类中的公有方法 光靠该注解并不足以实现事务
    仅是一个元数据,运行时架构会使用它配置具有事务行为的Bean
  • 支持特性
    • 传播类型
    • 隔离级别
    • 操作超时
    • 只读标记

缓存注解

@Cacheable

  • 方法级别

@CachePut

  • 方法级别,更新缓存

@CacheEvict

  • 方法级别,清除缓存

@CacheConfig

  • 类级别,缓存配置

SpringBoot注解

@EnableAutoConfiguration

  • 自动配置注解,可以将该注解配合@Configuration来完成配置类的自动装配

@SpringBootApplication

  • 该注解是以下三个注解的合并体@SpringBootConfigurationEnableAutoConfiguration@ComponentScan

Spring Cloud注解

@EnableConfigServer

  • 配置服务器

@EnableEurekaServer

  • 服务注册

@EnableDiscoveryClient

  • 服务发现

@EnableCircuitBreaker

  • 熔断器

@HystrixCommand

  • 服务降级

异常注解

@ExceptionHandler

  • 捕获异常进行处理

@ControllerAdvice

  • 统一异常处理,一般与@ExceptionHandler一起使用

@RestControllerAdvice

微服务和传统web优缺点

微服务

传统 web 开发

特点

  1. 功能都在一个包中
  2. 没有外部依赖
  3. 部署在一个 servlet 容器中

优势

  1. 集中式管理
  2. 基本不会重复开发
  3. 功能在本地,不需要进行分布式管理

劣势

  1. 开发效率低
  2. 代码维护成本高
  3. 部署时间长
  4. 稳定性不高
  5. 拓展性不够

微服务开发

特点

  1. 多个独立服务组成一个完整的系统
  2. 独立部署
  3. 服务之间独立开发业务
  4. 分布式管理
  5. 按照业务划分服务

优势

  1. 开发简单
  2. 技术选择灵活
  3. 可用性高
  4. 按需求拓展业务

劣势

  1. 多个服务的运维难度
    • 单体架构只需要保证一个项目正常运行,微服务项目中需要保证多个正常运行
  2. 系统集成
  3. 测试
  4. 接口调整成本高
    • 服务A修改了接口,其他调用者也需要同步修改
  5. 服务间通讯
    • RPC \ REST
  6. 性能监控
  7. 拆分服务的难度
    • 单一职责
    • 界定业务范围
  8. 数据一致性
    • sagas 模式
  9. 公共代码的开发
    • 多个服务存在相同功能,该功能没有达到可以拆分微服务的程度.一般会使用公共库来解决,但是多语言环境下公共库似乎不是那么优秀.

什么是微服务?微服务之间是如何独立通讯的?

什么是微服务?微服务之间是如何独立通讯的?

  • Author: HuiFer
  • Description: 介绍微服务的定义,服务之间的通讯,对RPC 和

什么是微服务

微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足.
微服务是一种架构风格, 一个大型软件应用由多个服务单元组成. 系统中的服务单元可以单独部署, 各个服务单元之间是松耦合的.
微服务概念起源: Microservices

微服务之间是如何独立通讯的

同步

REST HTTP 协议

REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议.

  • RESTFUL特点
  1. 每一个URI代表1种资源
  2. 客户端使用 GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源(也可以用于更新资源), PUT 用来更新资源, DELETE 用来删除资源
  3. 通过操作资源的表现形式来操作资源
  4. 资源的表现形式是 XML 或者 HTML
  5. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
例子
  • 有一个服务方提供了如下接口.
@RestController
@RequestMapping("/communication")
public class RestControllerDemo {
    @GetMapping("/hello")
    public String s() {
        return "hello";
    }
}
  • 另外一个服务需要去调用该接口, 调用方只需要根据 API 文档发送请求即可获取返回结果.
@RestController
@RequestMapping("/demo")
public class RestDemo{
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/hello2")
    public String s2() {
        String forObject = restTemplate.getForObject("http://localhost:9013/communication/hello", String.class);
        return forObject;
    }
}
  • 通过这样的方式可以实现服务之间的通讯

RPC TCP协议

RPC(Remote Procedure Call)远程过程调用, 简单的理解是一个节点请求另一个节点提供的服务

工作流程

  1. 执行客户端调用语句,传送参数
  2. 调用本地系统发送网络消息
  3. 消息传送到远程主机
  4. 服务器得到消息并取得参数
  5. 根据调用请求以及参数执行远程过程(服务)
  6. 执行过程完毕,将结果返回服务器句柄
  7. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
  8. 消息传回本地主机
  9. 客户端句柄由本地主机的网络服务接收消息
  10. 客户端接收到调用语句返回的结果数据
  • 这个不知道如何具体描述直接上代码.
  • 首先需要一个服务端
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * RPC 服务端用来注册远程方法的接口和实现类
 * @Date: 2019-11-04
 */
public class RPCServer {
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static final ConcurrentHashMap<String, Class> serviceRegister = new ConcurrentHashMap<>();

    /**
     * 注册方法
     * @param service
     * @param impl
     */
    public void register(Class service, Class impl) {
        serviceRegister.put(service.getSimpleName(), impl);
    }

    /**
     * 启动方法
     * @param port
     */
    public void start(int port) {
        ServerSocket socket = null;
        try {
            socket = new ServerSocket();
            socket.bind(new InetSocketAddress(port));
            System.out.println("服务启动");
            System.out.println(serviceRegister);
            while (true) {
                executor.execute(new Task(socket.accept()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static class Task implements Runnable {
        Socket client = null;

        public Task(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            ObjectInputStream input = null;
            ObjectOutputStream output = null;
            try {
                input = new ObjectInputStream(client.getInputStream());
                // 按照顺序读取对方写过来的内容
                String serviceName = input.readUTF();
                String methodName = input.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                Object[] arguments = (Object[]) input.readObject();
                Class serviceClass = serviceRegister.get(serviceName);
                if (serviceClass == null) {
                    throw new ClassNotFoundException(serviceName + " 没有找到!");
                }
                Method method = serviceClass.getMethod(methodName, parameterTypes);
                Object result = method.invoke(serviceClass.newInstance(), arguments);

                output = new ObjectOutputStream(client.getOutputStream());
                output.writeObject(result);
            } catch (Exception e) {
                e.printStackTrace();

            } finally {
                try {
                    // 这里就不写 output!=null才关闭这个逻辑了
                    output.close();
                    input.close();
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

}
  • 其次需要一个客户端
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * RPC 客户端
 * @Date: 2019-11-04
 */
public class RPCclient<T> {
    /**
     * 通过动态代理将参数发送过去到 RPCServer ,RPCserver 返回结果这个方法处理成为正确的实体
     */
    public static <T> T getRemoteProxyObj(final Class<T> service, final InetSocketAddress addr) {

        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Socket socket = null;
                ObjectOutputStream out = null;
                ObjectInputStream input = null;
                try {
                    socket = new Socket();
                    socket.connect(addr);

                    // 将实体类,参数,发送给远程调用方
                    out = new ObjectOutputStream(socket.getOutputStream());
                    out.writeUTF(service.getSimpleName());
                    out.writeUTF(method.getName());
                    out.writeObject(method.getParameterTypes());
                    out.writeObject(args);

                    input = new ObjectInputStream(socket.getInputStream());
                    return input.readObject();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    out.close();
                    input.close();
                    socket.close();
                }
                return null;
            }
        });

    }

}
  • 再来一个测试的远程方法
public interface Tinterface {
    String send(String msg);
}

public class TinterfaceImpl implements Tinterface {
    @Override
    public String send(String msg) {
        return "send message " + msg;
    }
}
  • 测试代码
import com.huifer.admin.rpc.Tinterface;
import com.huifer.admin.rpc.TinterfaceImpl;

import java.net.InetSocketAddress;

/**
 * @Date: 2019-11-04
 */
public class RunTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                RPCServer rpcServer = new RPCServer();
                rpcServer.register(Tinterface.class, TinterfaceImpl.class);
                rpcServer.start(10000);
            }
        }).start();
        Tinterface tinterface = RPCclient.getRemoteProxyObj(Tinterface.class, new InetSocketAddress("localhost", 10000));
        System.out.println(tinterface.send("rpc 测试用例"));


    }
}
  • 输出send message rpc 测试用例

异步

消息中间件

常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ , 常见的协议有AMQP、MQTTP、STOMP、XMPP. 这里不对消息队列进行拓展了, 具体如何使用还是请移步官网.

注解`Component`

  • 这是一段Component的源码内容,可以从源码知道需要关注的源码应该在org.springframework.context.annotation.ClassPathBeanDefinitionScanner
//....

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates that an annotated class is a "component".
 * Such classes are considered as candidates for auto-detection
 * when using annotation-based configuration and classpath scanning.
 *
 * <p>Other class-level annotations may be considered as identifying
 * a component as well, typically a special kind of component:
 * e.g. the {@link Repository @Repository} annotation or AspectJ's
 * {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation.
 *
 * @author Mark Fisher
 * @since 2.5
 * @see Repository
 * @see Service
 * @see Controller
 * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}

ClassPathBeanDefinitionScanner

  • 根据这个类的单词可以知道是一个Bean的扫描器,说到类扫描器可以知道有一个SpringBoot中的一个注解配置@SpringBootApplication(scanBasePackages = {"",""}) 这个配置在我的理解中是用来指定包的扫描路径。

构造方法

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		setEnvironment(environment);
		setResourceLoader(resourceLoader);
	}
  • 查看registerDefaultFilters()方法,方法在父类org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters
protected void registerDefaultFilters() {
// 下面一行能够让Component 相关注解生效而不是只有自己生效
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
// 包扫描扫出所有注解
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
  • metadata中就是注解信息
    image

Redis

Redis 技术栈

安装

yum & apt

  • apt-get install Redis-server
  • yum install Redis

编译

  1. 下载
  • wget http://download.Redis.io/releases/Redis-5.0.5.tar.gz
  1. 编译
  tar -zxvf Redis-5.0.5.tar.gz
  cd Redis-5.0.5
  make && make install
  1. 配置
  • vim Redis.conf
  1. 启动
  • Redis-server
  1. 关闭
  • Redis-cli shutdown

key 命令

keys

  • 搜索key根据匹配模式,例如: keys *

exists

  • 判断key是否存在,存在返回1,不存在返回0

del

  • 删除key

type

  • 获取key类型

randomkey

  • 随机返回key

expire

  • 设置key过期时间

sortzh

  • 字符串排序
  • 数字排序

服务端命令

flushall

  • 强制清空所有key(目标对象所有数据库)

flushdb

  • 清空当前数据库

client list

  • 连接信息列表

client kill

  • 关闭ip:port客户端

ping

  • 判断是否正常运行,正常运行返回pong

数据类型(操作命令非完整)

string

  • 一个key最大存储512M的数据

操作方法

  • get /set(单key操作)
  • mget / mset(多key操作)
  • getrange 获取指定下标范围内的字符串
  • setrange 设置指定下标范围内的字符串
  • setex => set expire 组合
  • incr 自增1
  • incrby 自增指定数字
  • decr 自减1
  • decrby 自减指定数字
  • append 追加字符串
  • strlen 返回字符串长度

list

  • blpop key 删除并获取第一个元素
  • brpop key 删除并获取最后一个元素
  • brpoplpush source destination 复制列表从source -> destination
  • lindex key index 根据key 和index 获取value
  • llen key 获取key的列表长度
  • lpop key 从左侧出列
  • lpush key value 从左侧入列
  • lrange key start stop 根据start,stop获取key一定范围内的列表
  • lset key index value 根据key + index 设置value
  • lrem key count value 删除列表元素
  • rpush等不做赘述

set

  • sadd key value 向key插入值
  • scard key 获取集合成员数量
  • sdiff key1 key2 返回集合差集
  • sinter key1 key2 返回集合交集
  • sunion key1 key2 返回集合并集
  • sismember key value 判断集合中是否包含value
  • smembers key 返回集合
  • srem key value 删除集合中的一个value

hash

  • hget key filed 获取hash中指定key->filed的值
  • hgetall key 获取hash中指定key的所有制
  • hexists key filed 查看是否存在filed
  • hdel key filed 删除指定key下的filed
  • hkeys key 获取所有filed
  • hset key filed value 设置key->filed->value

sortedSet

  • zadd key source member 向key追加source,member
  • zcard key 获取成员数量

数据存储(持久化)

RDB

将某个时间点的所有数据都以二进制形式存放到硬盘上

  • 可以将快照复制到其它服务器从而创建具有相同数据的服务器副本.
  • 如果系统发生故障,将会丢失最后一次创建快照之后的数据.
  • 如果数据量很大,保存快照的时间会很长,建议异步写入.
  • 存在的问题:时间、性能开销大,不可控且容易丢失数据.
  • 同步机制
    • save 命令,阻塞其他命令,直到save命令结束
    • bgsave 命令,异步化,创建子线程进行持久化,不会阻塞其他命令
  • 自动化触发
    • 根据修改数量、时间进行命令执行
    • save 900 1 # 900秒之内,对数据库进行了一次修改就执行 bgsave 命令
    • save 300 10 # 300秒之内,对数据库进行了十次修改就执行 bgsave 命令
    • save 60 10000 # 60秒之内,对数据库进行了一万次修改就执行 bgsav e命令

AOF

将写命令添加到 AOF 文件(Append Only File)的末尾(MySQL Binlog、HBase HLog).

  • 随着服务器写请求的增多,AOF 文件会越来越大.Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令
  • 使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上.这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘.

同步机制

  • always 每个命令都同步
  • eversec 每秒同步
  • no 操作系统决定同步时间

AOF重写

  • 对多条原生命令进行优化,重写成简化的命令以减少磁盘占用量、提高故障恢复效率.
  • 当 AOF 文件过大或增长速度过快时自动触发
  • 配置
    • auto-aof-rewrite-min-size:AOF 文件重写需要的大小

    • auto-aof-rewrite-percentage:AOF 文件增长率

    • aofcurrentsize:AOF 当前大小

    • aof-base-size:AOF 上次启动和重写的大小

  • 触发条件
    • aof_current_size > auto-aof-rewrite-min-size
    • aof_current_size - aof_base_size/aof_base_size > auto-aof-rewrite-percentage

集群

主从复制

主从链(拓扑结构)

主从

主从

复制模式

  • 全量复制:master 全部同步到 slave
  • 部分复制:slave 数据丢失进行备份

问题点

  • 同步故障
    • 复制数据延迟(不一致)
    • 读取过期数据(Slave 不能删除数据)
    • 从节点故障
    • 主节点故障
  • 配置不一致
    • maxmemory 不一致:丢失数据
    • 优化参数不一致:内存不一致.
  • 避免全量复制
    • 选择小主节点(分片)、低峰期间操作.
    • 如果节点运行 id 不匹配(如主节点重启、运行 id 发送变化),此时要执行全量复制,应该配合哨兵和集群解决.
    • 主从复制挤压缓冲区不足产生的问题(网络中断,部分复制无法满足),可增大复制缓冲区( rel_backlog_size 参数).
  • 复制风暴

哨兵机制

拓扑图

image

节点下线

  • 客观下线
    • 所有 Sentinel 节点对 Redis 节点失败要达成共识,即超过 quorum 个统一.
  • 主管下线
    • 即 Sentinel 节点对 Redis 节点失败的偏见,超出超时时间认为 Master 已经宕机.

leader选举

  • 选举出一个 Sentinel 作为 Leader:集群中至少有三个 Sentinel 节点,但只有其中一个节点可完成故障转移.通过以下命令可以进行失败判定或领导者选举.
  • 选举流程
    1. 每个主观下线的 Sentinel 节点向其他 Sentinel 节点发送命令,要求设置它为领导者.
    2. 收到命令的 Sentinel 节点如果没有同意通过其他 Sentinel 节点发送的命令,则同意该请求,否则拒绝.
    3. 如果该 Sentinel 节点发现自己的票数已经超过 Sentinel 集合半数且超过 quorum,则它成为领导者.
    4. 如果此过程有多个 Sentinel 节点成为领导者,则等待一段时间再重新进行选举.

故障转移

  • 转移流程
    1. Sentinel 选出一个合适的 Slave 作为新的 Master(slaveof no one 命令).
    2. 向其余 Slave 发出通知,让它们成为新 Master 的 Slave( parallel-syncs 参数).
    3. 等待旧 Master 复活,并使之称为新 Master 的 Slave.
    4. 向客户端通知 Master 变化.
  • 从 Slave 中选择新 Master 节点的规则(slave 升级成 master 之后)
    1. 选择 slave-priority 最高的节点.
    2. 选择复制偏移量最大的节点(同步数据最多).
    3. 选择 runId 最小的节点.

读写分离

定时任务

  • 每 1s 每个 Sentinel 对其他 Sentinel 和 Redis 执行 ping,进行心跳检测.
  • 每 2s 每个 Sentinel 通过 Master 的 Channel 交换信息(pub - sub).
  • 每 10s 每个 Sentinel 对 Master 和 Slave 执行 info,目的是发现 Slave 节点、确定主从关系.

分布式集群(Cluster)

拓扑图

image

通讯

集中式

将集群元数据(节点信息、故障等等)几种存储在某个节点上.

  • 优势
    1. 元数据的更新读取具有很强的时效性,元数据修改立即更新
  • 劣势
    1. 数据集中存储
Gossip

image

寻址分片

hash取模
  • hash(key)%机器数量
  • 问题
    1. 机器宕机,造成数据丢失,数据读取失败
    2. 伸缩性
一致性hash
  • image

  • 问题

    1. 一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。
      • 解决方案
        • 可以通过引入虚拟节点机制解决:即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
hash槽
  • CRC16(key)%16384

image

使用场景

热点数据

会话维持 session

分布式锁 SETNX

表缓存

消息队列 list

计数器 string

缓存设计

更新策略

  • LRU、LFU、FIFO 算法自动清除:一致性最差,维护成本低.
  • 超时自动清除(key expire):一致性较差,维护成本低.
  • 主动更新:代码层面控制生命周期,一致性最好,维护成本高.

更新一致性

  • 读请求:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应.
  • 写请求:先删除缓存,然后再更新数据库(避免大量地写、却又不经常读的数据导致缓存频繁更新).

缓存粒度

  • 通用性:全量属性更好.
  • 占用空间:部分属性更好.
  • 代码维护成本.

缓存穿透

当大量的请求无命中缓存、直接请求到后端数据库(业务代码的 bug、或恶意攻击),同时后端数据库也没有查询到相应的记录、无法添加缓存.
这种状态会一直维持,流量一直打到存储层上,无法利用缓存、还会给存储层带来巨大压力.

解决方案

  1. 请求无法命中缓存、同时数据库记录为空时在缓存添加该 key 的空对象(设置过期时间),缺点是可能会在缓存中添加大量的空值键(比如遭到恶意攻击或爬虫),而且缓存层和存储层数据短期内不一致;
  2. 使用布隆过滤器在缓存层前拦截非法请求、自动为空值添加黑名单(同时可能要为误判的记录添加白名单).但需要考虑布隆过滤器的维护(离线生成/ 实时生成).

缓存雪崩

缓存崩溃时请求会直接落到数据库上,很可能由于无法承受大量的并发请求而崩溃,此时如果只重启数据库,或因为缓存重启后没有数据,新的流量进来很快又会把数据库击倒

出现后应对

  • 事前:Redis 高可用,主从 + 哨兵,Redis Cluster,避免全盘崩溃.
  • 事中:本地 ehcache 缓存 + hystrix 限流 & 降级,避免数据库承受太多压力.
  • 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据.

请求过程

  1. 用户请求先访问本地缓存,无命中后再访问 Redis,如果本地缓存和 Redis 都没有再查数据库,并把数据添加到本地缓存和 Redis;
  2. 由于设置了限流,一段时间范围内超出的请求走降级处理(返回默认值,或给出友情提示).

事件型驱动

文件

时间

调度&执行

如何限流?在工作中是怎么做的?说一下具体的实现?

如何限流?在工作中是怎么做的?说一下具体的实现?

  • Author: HuiFer
  • Description: 该文简单介绍限流相关技术以及实现

什么是限流

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。

限流方法

计数器

实现方式

  • 控制单位时间内的请求数量
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    /**
     * 最大访问数量
     */
    private final int limit = 10;
    /**
     * 访问时间差
     */
    private final long timeout = 1000;
    /**
     * 请求时间
     */
    private long time;
    /**
     * 当前计数器
     */
    private AtomicInteger reqCount = new AtomicInteger(0);

    public boolean limit() {
        long now = System.currentTimeMillis();
        if (now < time + timeout) {
            // 单位时间内
            reqCount.addAndGet(1);
            return reqCount.get() <= limit;
        } else {
            // 超出单位时间
            time = now;
            reqCount = new AtomicInteger(0);
            return true;
        }
    }
}
  • 劣势
    • 假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 n-1 (n为限流请求数量),在下一分钟的 00:01 发送n个请求,这样在2秒钟内请求到达了 2n - 1 个.
      • 设每分钟请求数量为60个,每秒可以处理1个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时2秒钟有120个请求(每秒60个请求),远远大于了每秒钟处理数量的阈值

滑动窗口

实现方式

  • 滑动窗口是对计数器方式的改进,增加一个时间粒度的度量单位
    • 把一分钟分成若干等分(6份,每份10秒), 在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加1.当等分数量越大限流统计就越详细
package com.example.demo1.service;

import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.IntStream;

public class TimeWindow {
    private ConcurrentLinkedQueue<Long> queue = new ConcurrentLinkedQueue<Long>();

    /**
     * 间隔秒数
     */
    private int seconds;

    /**
     * 最大限流
     */
    private int max;

    public TimeWindow(int max, int seconds) {
        this.seconds = seconds;
        this.max = max;

        /**
         * 永续线程执行清理queue 任务
         */
        new Thread(() -> {
            while (true) {
                try {
                    // 等待 间隔秒数-1 执行清理操作
                    Thread.sleep((seconds - 1) * 1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                clean();
            }
        }).start();

    }

    public static void main(String[] args) throws Exception {

        final TimeWindow timeWindow = new TimeWindow(10, 1);

        // 测试3个线程
        IntStream.range(0, 3).forEach((i) -> {
            new Thread(() -> {

                while (true) {

                    try {
                        Thread.sleep(new Random().nextInt(20) * 100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    timeWindow.take();
                }

            }).start();

        });

    }

    /**
     * 获取令牌,并且添加时间
     */
    public void take() {


        long start = System.currentTimeMillis();
        try {


            int size = sizeOfValid();
            if (size > max) {
                System.err.println("超限");

            }
            synchronized (queue) {
                if (sizeOfValid() > max) {
                    System.err.println("超限");
                    System.err.println("queue中有 " + queue.size() + " 最大数量 " + max);
                }
                this.queue.offer(System.currentTimeMillis());
            }
            System.out.println("queue中有 " + queue.size() + " 最大数量 " + max);

        }

    }


    public int sizeOfValid() {
        Iterator<Long> it = queue.iterator();
        Long ms = System.currentTimeMillis() - seconds * 1000;
        int count = 0;
        while (it.hasNext()) {
            long t = it.next();
            if (t > ms) {
                // 在当前的统计时间范围内
                count++;
            }
        }

        return count;
    }


    /**
     * 清理过期的时间
     */
    public void clean() {
        Long c = System.currentTimeMillis() - seconds * 1000;

        Long tl = null;
        while ((tl = queue.peek()) != null && tl < c) {
            System.out.println("清理数据");
            queue.poll();
        }
    }

}

Leaky Bucket 漏桶

实现方式

  • 规定固定容量的桶,有水进入,有水流出. 对于流进的水我们无法估计进来的数量、速度,对于流出的水我们可以控制速度.
public class LeakBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * 水流出去的速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;


    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.max(0, (nowSize - (now - time) * rate));
        time = now;
        if ((nowSize + 1) < total) {
            nowSize++;
            return true;
        } else {
            return false;
        }

    }
}

Token Bucket 令牌桶

实现方式

  • 规定固定容量的桶,token 以固定速度往桶内填充,当桶满时 token 不会被继续放入,每过来一个请求把 token 从桶中移除,如果桶中没有 token 不能请求
public class TokenBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * token 放入速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;


    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.min(total, nowSize + (now - time) * rate);
        time = now;
        if (nowSize < 1) {
            // 桶里没有token
            return false;
        } else {
            // 存在token
            nowSize -= 1;
            return true;
        }
    }



}

工作中的使用

spring cloud gateway

  • spring cloud gateway 默认使用redis进行限流,笔者一般只是修改修改参数属于拿来即用.并没有去从头实现上述那些算法.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
  cloud:
    gateway:
      routes:
      - id: requestratelimiter_route
        uri: lb://pigx-upms
        order: 10000
        predicates:
        - Path=/admin/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1  # 令牌桶的容积
            redis-rate-limiter.burstCapacity: 3  # 流速 每秒
            key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
        - StripPrefix=1
@Bean
KeyResolver remoteAddrKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

sentinel

  • 通过配置来控制每个url的流量
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8720
      datasource:
        ds:
          nacos:
            server-addr: localhost:8848
            dataId: spring-cloud-sentinel-nacos
            groupId: DEFAULT_GROUP
            rule-type: flow
            namespace: xxxxxxxx
  • 配置内容在nacos上进行编辑
[
    {
        "resource": "/hello",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
  • resource:资源名,即限流规则的作用对象。
  • limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
  • grade:限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
  • count:限流阈值
  • strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
  • controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式)
  • clusterMode:是否为集群模式

总结

sentinel和spring cloud gateway两个框架都是很好的限流框架,但是在我使用中还没有将spring-cloud-alibaba接入到项目中进行使用,所以我会选择spring cloud gateway,当接入完整的或者接入Nacos项目使用setinel会有更加好的体验.

jenkins 启动脚本

#!/bin/bash
echo "Restarting SpringBoot Application"
pid=`ps -ef | grep jenkins-spring-boot-0.0.1-SNAPSHOT.jar | grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]
then
   kill -9 $pid
   echo "关闭进程:"$pid
fi

echo "授予当前用户权限"
# chmod 777 /var/lib/jenkins/workspace/jenkins-spring-boot/target/jenkins-spring-boot-0.0.1-SNAPSHOT.jar
echo "执行....."
nohup  java -jar /var/lib/jenkins/workspace/jenkins-spring-boot/target/jenkins-spring-boot-0.0.1-SNAPSHOT.jar --spring.config.location=/home/huifer/jenkins-spring-boot.yml > device-temp.txt 2>&1 &

echo "启动成功"

数据日志

import com.alibaba.fastjson.JSON;
import com.shands.mod.dao.mapper.hs.HsLogMapper;
import com.shands.mod.dao.model.hs.HsLog;
import java.lang.reflect.Method;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class LogAop {

  @Autowired
  private HsLogMapper hsLogMapper;
  @Autowired
  private ApplicationContext applicationContext;
  @Autowired
  private SqlSession sqlSession;

  @Pointcut(
      "execution(public * com.shands.mod.dao.mapper.hs.*.updateByPrimaryKeySelective(..))"
  )
  private void hsAopUpdateOne() {
  }

  @Pointcut(
      "execution(public * com.shands.mod.dao.mapper.hs.*.updateByPrimaryKey(..))"
  )
  private void hsAopUpdateTwo() {
  }

  @Before("hsAopUpdateTwo()  && args(base)")
  public void beforeOnce(JoinPoint jp, Object base) {
    try {

      ObjectId objectId = new ObjectId();
      BeanUtils.copyProperties(base, objectId);
      Signature signature = jp.getSignature();
      Class<?> aClass = signature.getDeclaringType();
      Method selectByPrimaryKey = aClass.getMethod("selectByPrimaryKey", Integer.class);

      Object invoke = selectByPrimaryKey.invoke(sqlSession.getMapper(aClass), objectId.id);
      HsLog hsLog = new HsLog();
      hsLog.setCreateTime(new Date());
      // 更新前的
      hsLog.setSource(JSON.toJSONString(invoke));
      // 更新后的
      hsLog.setTarget(JSON.toJSONString(base));
      hsLog.setMapperClass(aClass.getName());
      hsLogMapper.insert(hsLog);
    } catch (Exception e) {
      log.info("数据日志,{}", e);
    }
  }


  private class ObjectId {

    private Integer id;
  }
}

maven

maven

目录结构

source code

  • 源码存放目录 ${base_dir}/src/main/java

resources

  • 资源目录 ${base_dir}/src/main/resources

test

  • 测试代码目录 ${base_dir}/src/test

complied byte code

  • 编译后目录 ${base_dir}/target/

maven仓库

  • 本地仓库地址 ${home}/.m2/repository

pom 非完整

项目信息

groupId

artifactId

version

依赖 dependencies

dependency

依赖范围

  • compile
  • test
  • provided
  • runtime
  • system

插件 plugins

常用命令

单元测试

  • mvn test

项目打包

  • mvn package

生成jar

  • mvn install

编译

  • mvn compile

清除编译目录

  • mvn clean
    建议配合 mvn clean, mvn clean install

私有仓库

类型

hosted

  • releases
  • snapshot
  • 3rd party(内网使用)

virtual

proxy

  • **仓库下载文件存放 central
  • apache存储 apache snapshots
  • codehaus存储 codehaus snapshots

group

  • 分组模块

镜像地址

  • setting.xml 配置 <mirror>

Nginx 安装

依赖

  • gcc
  • pcre
  • zlib
  • OpenSSL

安装流程

tar -zxvf nginx-1.17.4.tar.gz
cd nginx-1.17.4
mkdir run
# 修改安装路径
./configure --prefix=/home/huifer/huifer/nginx-1.17.4/run
make && make install 

image

@ConfigurationProperties 和@Bean 构建配置文件

org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		ConfigurationProperties annotation = getAnnotation(bean, beanName,
				ConfigurationProperties.class);
		if (annotation != null) {
			bind(bean, beanName, annotation);
		}
		return bean;
	}

IMAP邮箱

package com.example.demo.entity;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;

import javax.mail.*;
import javax.mail.search.FlagTerm;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * IMAP 协议
 */
public class ImapFetchMail {
    public static void main(String[] args) throws Exception {
        String host = "imap.qq.com";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int port = 993;
        String username = "qq邮箱地址";
        String password = "填写密码";
        final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
        Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
        Properties props = System.getProperties();
        props.setProperty("mail.imap.socketFactory.class", SSL_FACTORY);
        props.setProperty("mail.imap.socketFactory.port", "993");
        props.setProperty("mail.store.protocol", "imap");
        props.setProperty("mail.imap.host", host);
        props.setProperty("mail.imap.port", "993");
        props.setProperty("mail.imap.auth.login.disable", "true");
        Session session = Session.getDefaultInstance(props, null);
        session.setDebug(false);
        IMAPFolder folder = null;
        IMAPStore store = null;
        String regex = "[a-zA-Z0-9_-]+@\\w+\\.[a-z]+(\\.[a-z]+)?";
        Pattern p = Pattern.compile(regex);
        IMAPFolder inbox = null;
        try {
            store = (IMAPStore) session.getStore("imap");  // 使用imap会话机制,连接服务器
            store.connect(host, port, username, password);
            Folder inbox1 = store.getFolder("INBOX");
            inbox1.open(Folder.READ_WRITE);
            FlagTerm ft =
                    new FlagTerm(new Flags(Flags.Flag.SEEN), false); //false代表未读,true代表已读
            Message[] messages = inbox1.getMessages();
            Message[] messages1 = messages;

            Message[] search = inbox1.search(ft);

            for (Message message : search) {
                IMAPMessage imes = (IMAPMessage) message;
                StringBuffer content = new StringBuffer(30);
                getAllMultipart(message, content);
                HashMap<String, String> res = new HashMap<>();
                res.put("title", imes.getSubject());
                res.put("messageId", imes.getMessageID());
                res.put("context", content.toString());
                res.put("send_time", sdf.format(imes.getSentDate()));
                Matcher m = p.matcher(String.valueOf(imes.getFrom()[0]));
                res.put("from", m.find() ? m.group() : "");
                System.out.println(res);

            }

            System.out.println();

        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (MessagingException e) {
            e.printStackTrace();
        } finally {
            try {
                if (folder != null) {
                    folder.close(false);
                }
                if (store != null) {
                    store.close();
                }
            } catch (MessagingException e) {
                e.printStackTrace();
            }
        }
        System.out.println("接收完毕!");
    }




    private static void getAllMultipart(Part part, StringBuffer sb) throws Exception {
        String contentType = part.getContentType();
        int index = contentType.indexOf("name");
        boolean conName = false;
        if (index != -1) {
            conName = true;
        }
        //判断part类型
        if (part.isMimeType("text/plain") && !conName) {
            sb.append((String) part.getContent());
        } else if (part.isMimeType("text/html") && !conName) {
            sb.append((String) part.getContent());
        } else if (part.isMimeType("multipart/*")) {
            Multipart multipart = (Multipart) part.getContent();
            int counts = multipart.getCount();
            for (int i = 0; i < counts; i++) {
                //递归获取数据
                getAllMultipart(multipart.getBodyPart(i), sb);
                //附件可能是截图或上传的(图片或其他数据)
                if (multipart.getBodyPart(i).getDisposition() != null) {
                    //附件为截图
                    if (multipart.getBodyPart(i).isMimeType("image/*")) {
                        InputStream is = multipart.getBodyPart(i)
                                .getInputStream();
                        String name = multipart.getBodyPart(i).getFileName();
                        String fileName;
                        //截图图片
                        if (name.startsWith("=?")) {
                            fileName = name.substring(name.lastIndexOf(".") - 1, name.lastIndexOf("?="));
                        } else {
                            //上传图片
                            fileName = name;
                        }

                        FileOutputStream fos = new FileOutputStream("E:\\"
                                + fileName);
                        int len = 0;
                        byte[] bys = new byte[1024];
                        while ((len = is.read(bys)) != -1) {
                            fos.write(bys, 0, len);
                        }
                        fos.close();
                    } else {
                        //其他附件
                        InputStream is = multipart.getBodyPart(i)
                                .getInputStream();
                        String name = multipart.getBodyPart(i).getFileName();
                        FileOutputStream fos = new FileOutputStream("E:\\"
                                + name);
                        int len = 0;
                        byte[] bys = new byte[1024];
                        while ((len = is.read(bys)) != -1) {
                            fos.write(bys, 0, len);
                        }
                        fos.close();
                    }
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            getAllMultipart((Part) part.getContent(), sb);
        }
    }

}

动态实体

动态实体

目标

在原有数据基础上动态增加数据(删除).
应用场景: 在权限项目中可用来根据不同的设置来创建新的实体作为返回.

方法

通过 json工具库(fastjson,jackjson,gson...) 将查询出来的实体转换成 map 对象,将不允许显示的属性从 map 对象中移除. 通过 CGLIB 转换成新的实体即可

依赖

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.8</version>
</dependency>
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib-nodep</artifactId>
  <version>3.2.4</version>
</dependency>

编码

  • 核心工具类
import com.fasterxml.jackson.databind.ObjectMapper;
import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
//import org.springframework.cglib.beans.BeanGenerator;
//import org.springframework.cglib.beans.BeanMap;

import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Map;

public class ReflectUtil {


    public static Object getTarget(Object source, Map<String, Object> addProperties) {
        // 获取原始数据的value
        PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
        PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(source);
        Map<String, Class> propertyMap = new HashMap<>();
        for (PropertyDescriptor d : descriptors) {
            if (!"class".equalsIgnoreCase(d.getName())) {
                propertyMap.put(d.getName(), d.getPropertyType());
            }
        }
        // 增加数据的值
        addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));
        // 创建原始数据的实体对象
        DynamicBean dynamicBean = new DynamicBean(source.getClass(), propertyMap);
        // 老数据
        propertyMap.forEach((k, v) -> {
            try {
                if (!addProperties.containsKey(k)) {
                    dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(source, k));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 添加新数据
        addProperties.forEach((k, v) -> {
            try {
                dynamicBean.setValue(k, v);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Object target = dynamicBean.getTarget();
        return target;
    }


    public static void main(String[] args) throws Exception {
        BaseBase entity = new BaseBase();
        Map<String, Object> addProperties = new HashMap() {{
            put("username", "username");
            put("pwd", "pwd");
            put("hcName", "hcName");
            put("hcName1", "hcName");
            put("hcName2", "hcName");
        }};
        Object target = getTarget(entity, addProperties);
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(target);
        System.out.println(json);
        StuFat stuFat = new StuFat();
        BeanUtils.copyProperties(stuFat, target);
        System.out.println();
    }


    /**
     * 动态bean
     */
    public static class DynamicBean {
        /**
         * 目标对象
         */
        private Object target;

        /**
         * 属性集合
         */
        private BeanMap beanMap;

        public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
            this.target = generateBean(superclass, propertyMap);
            this.beanMap = BeanMap.create(this.target);
        }


        /**
         * bean 添加属性和值
         *
         */
        public void setValue(String property, Object value) {
            beanMap.put(property, value);
        }

        /**
         * 获取属性值
         *
         */
        public Object getValue(String property) {
            return beanMap.get(property);
        }

        /**
         * 获取对象
         *
         */
        public Object getTarget() {
            return this.target;
        }


        /**
         * 根据属性生成对象
         *
         */
        private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
            BeanGenerator generator = new BeanGenerator();
            if (null != superclass) {
                generator.setSuperclass(superclass);
            }
            BeanGenerator.addProperties(generator, propertyMap);
            return generator.create();
        }
    }

}
  • 空属性对象,通过一个空属性对象来放最后真正显示的数据
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class BaseBase {
}
  • 模拟数据实体
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
public class StuFat {
    private String username;
    private String pwd;
    private String hcName;
}
  • ReflectUtil.getTargetJSON.parseObject对比
    • 两者都可以通过 Map 的删除 key 来得到一个新的实体对象,但是两者存在差异,通过JSON.parseObject创建的对象属性值名称会被暴露但是值会变成空,ReflectUtil.getTarget就直接没有多余字段
  • JSON.parseObject结果如下
{
  "hcName": "zs",
  "pwd": "zs",
  "username": ""
}
  • ReflectUtil.getTarget结果如下
{
  "hcName": "zs",
  "pwd": "zs"
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
@RequestMapping("/hhh")
public class CController {
    @GetMapping("/dc")
    public Object json() {
        // 假设stuFat是查询出来的
        StuFat stuFat = new StuFat();
        stuFat.setUsername("zs");
        stuFat.setPwd("zs");
        stuFat.setHcName("zs");
        HashMap mapType = changeAttr(stuFat);
        // basebase 是没有字段的对象直接一个实体
        //{
        //  "hcName": "zs",
        //  "pwd": "zs"
        //}
        BaseBase entity = new BaseBase();
        Object target = ReflectUtil.getTarget(entity, mapType);
        return target;
    }

    /**
     * 修改实体返回一个HashMap
     * @return
     */
    private HashMap changeAttr(Object stuFat) {


        // 转换成map 对象,
        String jsonString = JSONObject.toJSONString(stuFat);
        HashMap mapType = JSON.parseObject(jsonString, HashMap.class);
        System.out.println(mapType);
        // 删除一个属性: 从map中删除
        mapType.remove("username");
        return mapType;
    }

    @GetMapping("/db")
    public Object jsonDB() {
        // 假设stuFat是查询出来的
        StuFat stuFat = new StuFat();
        stuFat.setUsername("zs");
        stuFat.setPwd("zs");
        stuFat.setHcName("zs");
        HashMap mapType = changeAttr(stuFat);
        // 通过 json实现 直接构建: 问题删除的对象会变成null,"" 等表示空的符号
        //{
        //  "hcName": "zs",
        //  "pwd": "zs",
        //  "username": ""
        //}
        String jsonString1 = JSONObject.toJSONString(mapType);
        StuFat stuFat1 = JSON.parseObject(jsonString1, StuFat.class);
        return stuFat1;
    }
}

RequestMapping

  • 细节正则表达式
@RestController
@RequestMapping("/demo")
public class DemoController {
    @GetMapping("/{id:\\d+}")
    public String obj(
            @PathVariable("id") String id
    ) {
        return id;
    }
}

通过:正则来进行url参数过滤,或者说url不存在

微服务治理策略

微服务治理策略

  • Author: HuiFer
  • Description: 该文简单介绍微服务的治理策略以及应用技术

服务的注册和发

解决问题: 集中管理服务

解决方法: eureka 、zookeeper

负载均衡

解决问题: 降低服务器硬件压力

解决方法: nginx 、 Ribbon

通讯

解决问题: 各个服务之间的沟通桥梁

解决方法 :

  • 同步消息
    1. rest
    2. rpc
  • 异步消息
    1. MQ

配置管理

解决问题: 随着服务的增加配置也在增加,如何管理各个服务的配置

解决方法: nacos 、 spring cloud config 、 Apollo

容错和服务降级

解决问题: 在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可以,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应.

解决方法: hystrix

服务依赖关系

解决问题: 多个服务之间来回依赖,启动关系的不明确

解决方法:

  1. 应用分层: 数据层,业务层 数据层不需要依赖业务层,业务层依赖数据,规定上下依赖关系避免循环圈

服务文档

解决问题: 降低沟通成本

解决方法: swagger 、 java doc

服务安全问题

解决问题: 敏感数据的安全性

解决方法: oauth 、 shiro 、 spring security

流量控制

解决问题: 避免一个服务上的流量过大拖垮整个服务体系

解决方法: Hystrix

自动化测试

解决问题: 提前预知异常,确定服务是否可用

解决方法: junit

服务上线,下线的流程

解决问题: 避免服务随意的上线下线

解决方法: 新服务上线需要经过管理人员审核.服务下线需要告知各个调用方进行修改,直到没有调用该服务才可以进行下线.

兼容性

解决问题: 服务开发持续进行如何做到兼容

解决方法: 通过版本号的形式进行管理,修改完成进行回归测试

服务编排

解决问题: 解决服务依赖问题的一种方式

解决方法: docker & k8s

资源调度

解决问题: 每个服务的资源占用量不同,如何分配

解决方法: JVM 隔离、classload 隔离 ; 硬件隔离

容量规划

解决问题: 随着时间增长,调用逐步增加,什么时候追加机器

解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值,超过阈值就可以追加机器

git版本管理

基于GIT版本管理

Git是一个 “分布式版本管理工具”,简单的理解版本管理工具:大家在写东西的时候都用过 “回撤” 这个功能,但是回撤只能回撤几步,假如想要找回我三天之前的修改,光用 “回撤” 是找不回来的。而 “版本管理工具” 能记录每次的修改,只要提交到版本仓库,你就可以找到之前任何时刻的状态(文本状态)。

开卷必读

  • 如果你还没有使用过GIT或者不知道Git是什么可以看一下这篇文章:git 简单使用教程

git commit 语义化提交日志

  • 每次提交都需要携带chore(维护)、docs(文档)、feat(新功能)、fix(bug修复)、refactor(重构)、style(样式)、test(测试) 其中一个,不可多个重复在一个commit中!

语义化版本号

以 v1.5.2 为例,1.5.2 按照英文句号分割为三部分:

  • 主版本号::是你对项目做了不兼容的 API 修改,即大版本的升级。
  • 次版本号:当你做了向下兼容的功能性新增。即,新增了功能,但是不影响旧有功能的使用。
  • 修订号:你做了向下兼容的问题修正。即,bug fix 版本。没有新增功能,只是修复了历史遗漏 BUG。

git分支模型

Vincent Driessen 提出的git 管理流程和规范

img

图解

图中我们可以看到分支有masterhotfixdevelopreleasefeature 5个分支

  • master

    • 该分支不可提交代码只能从其他分支合并(dev&hotfix&release)
    • 管理员可操作
  • hotfix

    • 当我们发现master上存在bug时新建hotfix分支进行修复修复完成后合并回masterdevelop分支
    • 开发人员操作
  • develop

    • 开发分支,主要用来合并feature分支,当代码量或者开发进度到达一定程度可以发布此时可以合并到master分支
    • 开发人员操作
  • release

    • 发布版本分支
    • 管理员可操作
  • feature

    • 该分支用来开发新功能,新功能完成后合并到develop分支
    • 开发人员操作
  • tag

    • 标签用来标识版本号
    • 都可以操作

git flow 解决的问题

  1. 新功能如何开发
  2. 功能之间不会互相影响
  3. 对分支的语义化描述
  4. 发布版本管理
  5. 线上代码bug修复

推荐软件

SourceTree

代码审计 code review

目标

  • 提高代码质量,及早发现潜在缺陷,降低修改/弥补缺陷的成本

  • 促进团队内部知识共享,提高团队整体水平

  • 评审过程对于评审人员来说,也是一种思路重构的过程,帮助更多的人理解系统

  • 是一个传递知识的手段,可以让其它并不熟悉代码的人知道作者的意图和想法,从而可以在以后轻松维护代码

  • 可以被用来确认自己的设计和实现是一个清楚和简单的

  • 鼓励相互学习对方的长处和优点

  • 高效迅速完成Code Review

Pull Request(PR)

  • 通过PR的方式进行code review
  • 一个PR中不可以存在多个任务
  • PR发起后在1-2天内合并或者拒绝
    • 拒绝告知具体原因

Code Review Checklist

完整性检查
  1. 是否实现当前版本提出功能
  2. 是否修改bug
  3. 是否创建数据库、表
  4. 是否存在初始化数据需要创建
  5. 是否存在任何没有定义或没有引用到的变量、常数或数据类型
一致性检查
  • 是否符合提出功能
  • 使用的格式、符号、结构是否一致(如tab是2个空格还是4个或者其他)建议使用 editorconfig文件进行管理
正确性检查
  • 注释正确性
  • 参数正确性
可修性改检查
  • 代码涉及到的常量是否易于修改(如使用配置、定义为类常量、使用专门的常量类等)
可预测性检查
  • 死循环
  • 无穷递归
健壮性检查
  • 资源释放
  • 异常处理
结构性检查
  • 循环只有一个入口
  • 功能模块的辨识度
可追溯检查
  • 命名唯一性
  • 修订历史记录
可理解性检查
  • 函数、类的注解
  • 删除不使用的代码
  • 统一缩进
  • 变量的取值范围
可重用检查
  • 同一段代码不重复出现
  • 抽象可重用函数,类
安全性检查
  • 硬编码用于测试的删除
  • 敏感数据
性能检查
  • 选择合适的数据类型
  • 懒加载
  • 异步
  • 并行
  • 缓存

推荐插件

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.