《SpringBoot2从入门到工程实战》第六篇:集成MyBatis

最近事情太多了,公司的,家庭的,导致这个系列暂停了,最近准备抽时间,把这个系列继续写下去、写完。

本章我们介绍SpringBoot与MyBatis的集成。

本章示例工程名称:springboot_worker_mybatis

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:

MySQL建表语句:

CREATE DATABASE test_201908;

USE test_201908;

CREATE TABLE `t_article_201908_001` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL DEFAULT '',
  `summary` varchar(1024) NOT NULL DEFAULT '',
  `status` int(11) NOT NULL DEFAULT '0',
  `type` int(11) NOT NULL,
  `user_id` bigint(20) NOT NULL DEFAULT '0',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `public_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_mybatis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_mybatis</name>
  <url>http://maven.apache.org</url>


	<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
      <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        
	    <dependency>  
	        <groupId>org.springframework.boot</groupId>  
	        <artifactId>spring-boot-starter-jdbc</artifactId>  
	    </dependency>  
	    
	    <dependency>  
	        <groupId>mysql</groupId>  
	        <artifactId>mysql-connector-java</artifactId>  
	        <version>8.0.15</version>
	    </dependency> 
	    
	    <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>druid-spring-boot-starter</artifactId>
		    <version>1.1.13</version>
	    </dependency>
	    
	    <dependency>
	      <groupId>org.mybatis.spring.boot</groupId>
	      <artifactId>mybatis-spring-boot-starter</artifactId>
	      <version>1.3.3</version>
	    </dependency>
    
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <scope>test</scope>
        </dependency>
    </dependencies>
    <!-- 
    使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
    提示了xxxxxx.jar中没有主清单属性.
    添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
    -->
    <build>
      <plugins>
          <plugin>
              <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
      </plugins>
     </build>
</project>

ArticleModel.java内容:

package com.stamhe.springboot.model;

import java.util.Date;

/*
CREATE TABLE `t_article_201908_001` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL DEFAULT '',
  `summary` varchar(1024) NOT NULL DEFAULT '',
  `status` int(11) NOT NULL DEFAULT '0',
  `type` int(11) NOT NULL,
  `user_id` bigint(20) NOT NULL DEFAULT '0',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `public_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 */
public class ArticleModel {
	private Long id;
    private String title;
    private String summary;
    private Integer status;
    private Integer type;
    private Long userId;
    
    private Date createTime;
    private Date publicTime;
    private Date updateTime;
    
    
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getSummary() {
		return summary;
	}
	public void setSummary(String summary) {
		this.summary = summary;
	}
	public Integer getStatus() {
		return status;
	}
	public void setStatus(Integer status) {
		this.status = status;
	}
	public Integer getType() {
		return type;
	}
	public void setType(Integer type) {
		this.type = type;
	}
	public Long getUserId() {
		return userId;
	}
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	public Date getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}
	public Date getPublicTime() {
		return publicTime;
	}
	public void setPublicTime(Date publicTime) {
		this.publicTime = publicTime;
	}
	public Date getUpdateTime() {
		return updateTime;
	}
	public void setUpdateTime(Date updateTime) {
		this.updateTime = updateTime;
	}
	
	@Override
	public String toString() {
		return "ArticleModel [id=" + id + ", title=" + title + ", summary=" + summary + ", status=" + status + ", type="
				+ type + ", userId=" + userId + ", createTime=" + createTime + ", publicTime=" + publicTime
				+ ", updateTime=" + updateTime + "]";
	}
}

ArticleMapper.java内容:

package com.stamhe.springboot.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.Update;

import com.stamhe.springboot.model.ArticleModel;

/*
CREATE TABLE `t_article_201908_001` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL DEFAULT '',
  `summary` varchar(1024) NOT NULL DEFAULT '',
  `status` int(11) NOT NULL DEFAULT '0',
  `type` int(11) NOT NULL,
  `user_id` bigint(20) NOT NULL DEFAULT '0',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `public_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 */
public interface ArticleMapper 
{
	@Select("select * from t_article_201908_001 where id=#{id}")
	@Results({
		@Result(property="userId", column="user_id"),
		@Result(property="createTime", column="create_time"),
		@Result(property="updateTime", column="update_time"),
		@Result(property="publicTime", column="public_time"),
	})
	public ArticleModel getDetail(Long id);
	

	@Select("select * from t_article_201908_001")
	@Results({
		@Result(property="userId", column="user_id"),
		@Result(property="createTime", column="create_time"),
		@Result(property="updateTime", column="update_time"),
		@Result(property="publicTime", column="public_time"),
	})
	public List<ArticleModel> getAll();
	
	// articleModel.getId()
	@Insert("insert into t_article_201908_001 (title, summary, status, type, user_id, create_time, update_time, public_time) "
			+ "values (#{title}, #{summary}, #{status}, #{type}, #{userId}, #{createTime}, #{updateTime}, #{publicTime})")
	@SelectKey(statement="select LAST_INSERT_ID()", keyProperty="id", before=false, resultType=long.class)
	public void insert(ArticleModel articleModel);
	
	// 返回受影响的行数
	@Update("update t_article_201908_001 set summary=#{summary} where id=#{id}")
	public Integer update(ArticleModel articleModel);
	
	// 返回受影响的行数
	@Delete("delete from t_article_201908_001 where id=#{id}")
	public Integer delete(@Param("id")Long id);
}

ArticleController.java内容:

package com.stamhe.springboot.controller;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.stamhe.springboot.mapper.ArticleMapper;
import com.stamhe.springboot.model.ArticleModel;

@RestController
@RequestMapping("/article")
public class ArticleController {
	
	@Autowired
	private ArticleMapper articleMapper;

	// http://localhost:8080/article/add
	@RequestMapping("/add")
	public Long addAction()
	{
		ArticleModel articleModel = new ArticleModel();
		articleModel.setTitle("测试标题-201908-006-003");
		articleModel.setSummary("测试摘要-201908-006-003");
		articleModel.setStatus(1);
		articleModel.setType(1);
		articleModel.setUserId(201908006002L);
		articleModel.setCreateTime(new Date());
		articleModel.setUpdateTime(new Date());
		articleModel.setPublicTime(new Date());
        articleMapper.insert(articleModel);
        Long id = articleModel.getId();
        return id;
	}
	
	// http://localhost:8080/article/detail/1
	@RequestMapping(value="/detail/{id}")
	public ArticleModel detailAction(@PathVariable("id")Long id)
	{
		ArticleModel articleModel = articleMapper.getDetail(id);
		return articleModel;
	}
}

App.java内容:

package com.stamhe.springboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({"com.stamhe.springboot"})
@MapperScan("com.stamhe.springboot.mapper")
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

application.properties内容:

spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test_201908?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.username = root
spring.datasource.password = 
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource

mybatis.type-aliases-package = com.stamhe.springboot.model

访问 http://localhost:8080/article/add 得到如下结果:

访问 http://localhost:8080/article/detail/1 得到如下结果:

《SpringBoot2从入门到工程实战》第五篇:集成多实例Memcached

上一章我们介绍了多实例 Redis 的操作集成,本章,我们继续介绍缓存的另外一个集大成者——Memcached的使用。

作为现代互联网的基石软件之一,Memcached以其高性能、稳定性、易维护、使用简单、实现简单等诸多优点,现在仍然是大型业务系统的最重要数据存储软件之一,特别是在大型系统中,往往是几百个、几千个Memcached的实例对外提供访问来达到降低对MySQL的直接访问冲击。

在本章中我们介绍的是基于一致性Hash规则的Memcached集群的使用,如果你的业务是使用的Proxy代理模式,其实也是一样的。

本章示例工程名称:springboot_worker_multi_memcached

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_multi_memcached</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_multi_memcached</name>
  <url>http://maven.apache.org</url>

  <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
  </properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

  <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- http://tengj.top/2017/04/05/springboot7/ -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-logging</artifactId>
	</dependency>
    
	<dependency>
		<groupId>net.spy</groupId>
		<artifactId>spymemcached</artifactId>
		<version>2.12.3</version>
	</dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

	<!-- 
	使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
	提示了xxxxxx.jar中没有主清单属性.
	添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
	-->
	<build>
	  <plugins>
	  	<plugin>
	  		<groupId>org.springframework.boot</groupId>
	 		<artifactId>spring-boot-maven-plugin</artifactId>
	  	</plugin>
	  </plugins>
 	</build>
</project>

memcached的操作库,我们引入的是 spymemcached这个通用的依赖库。

App.java内容:

package com.stamhe.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication
@ComponentScan({"com.stamhe.springboot"})
@PropertySource({
	"classpath:/memcached.properties"
})
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

引入了独立的 memcached.properties 配置文件。

MemcachedInit.java内容:

package com.stamhe.springboot.init;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import net.spy.memcached.MemcachedClient;

@Component
public class MemcachedInit implements CommandLineRunner {
	@Value("${springboot.memcached1.host}")
	String mhost1;
	
	@Value("${springboot.memcached1.port}")
	int mport1;

	@Value("${springboot.memcached2.host}")
	String mhost2;

	@Value("${springboot.memcached2.port}")
	int mport2;
	
	private MemcachedClient client;

	@Override
	public void run(String... args) throws Exception {
		try {
			// 一致性hash
			List<InetSocketAddress> list = new ArrayList<>();
			list.add(new InetSocketAddress(mhost1, mport1));
			list.add(new InetSocketAddress(mhost2, mport2));
			client = new MemcachedClient(list);
        } catch (Exception e) {
            System.out.println("inint MemcachedClient failed. error = " + e.getMessage());
        }
	}
	
	public MemcachedClient getClient() {
		return client;
	}
}

初始化Memcached连接参数,这儿使用了一致性hash。

HelloController.java内容:

package com.stamhe.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.stamhe.springboot.init.MemcachedInit;
import net.spy.memcached.MemcachedClient;

@RestController
@RequestMapping("/hello")
public class HelloController {
	@Autowired
	private MemcachedInit memcachedInit;

	@RequestMapping("/cache")
	public String cacheAction()
	{
		MemcachedClient cacheObj = memcachedInit.getClient();
		String key = "k1";
		// 过期时间单位: ms
		cacheObj.set(key, 60000, "v1");
		String value = cacheObj.get(key).toString();
		
		return value;
	}
}

操作Memcached的示例。

memcached.properties内容:

springboot.memcached1.host=127.0.0.1
springboot.memcached1.port=11211

springboot.memcached2.host=127.0.0.1
springboot.memcached2.port=11211

这儿为了方便,两个memcached实例都使用的相同的连接参数。

访问 http://localhost:8080/hello/cache 得到如下结果:

《SpringBoot2从入门到工程实战》第四篇:集成多实例Redis

作为一种高性能的缓存数据库,Redis以其简单、可靠、高性能、数据结构多等很多优点,收到越来越多开发者的喜爱,所以我们优先介绍在SpringBoot对于Redis的集成使用,并且是多Redis实例的使用实战。

本章示例工程名称:springboot_worker_multi_redis

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_multi_redis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_multi_redis</name>
  <url>http://maven.apache.org</url>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
	</properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

  	<dependencies>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
    
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	    
	    <!-- Spring Boot2.x 后底层不再是Jedis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency> 
        
        <dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.8</version>
		</dependency>
        
        <!-- 设置了连接池的相关参数,需要额外引入依赖 -->
        <dependency>
    		<groupId>org.apache.commons</groupId>
    		<artifactId>commons-pool2</artifactId>
		</dependency>
    
	    <dependency>
	    	<groupId>junit</groupId>
	      	<artifactId>junit</artifactId>
	      	<scope>test</scope>
	    </dependency>
	</dependencies>

	<!-- 
	使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
	提示了xxxxxx.jar中没有主清单属性.
	添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
	-->
	<build>
	  <plugins>
	  	<plugin>
	  		<groupId>org.springframework.boot</groupId>
	 		<artifactId>spring-boot-maven-plugin</artifactId>
	  	</plugin>
	  </plugins>
 	</build>
</project>

新增了 spring-boot-starter-data-redis 以及 commons-pool2 两个依赖,以及存储redis所需要的序列化方案json库,这儿我们选用比较通用的jackon库。

App.java内容:

package com.stamhe.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication
@PropertySource({
	"classpath:/redis.properties"
})
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

这儿我们使用了独立的 redis.properties 来放置 Redis 相关的配置信息,方便维护,同时为了方便,我们将其放置在了classpath 路径,真实项目中,应该放到 /data、/opt这样的目录去,这个我们在第二篇中有详细的说明。

redis.properties内容:

# REDIS (RedisProperties)
### 单机版
# Redis数据库索引(默认为0)
spring.redis1.database=1
# 连接URL,将覆盖主机,端口和密码(用户将被忽略),例如:redis://user:password@example.com:6379
#spring.redis.url=
# Redis服务器地址
spring.redis1.host=127.0.0.1
# Redis服务器连接端口
spring.redis1.port=6379
# Redis服务器连接密码(默认为空)
spring.redis1.password=
# 数据库连接超时时间,2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位. 单位: 毫秒
spring.redis1.timeout=500
# 启用SSL支持。
#spring.redis.ssl=false


# REDIS (RedisProperties)
### 单机版
# Redis数据库索引(默认为0)
spring.redis2.database=2
# 连接URL,将覆盖主机,端口和密码(用户将被忽略),例如:redis://user:password@example.com:6379
#spring.redis.url=
# Redis服务器地址
spring.redis2.host=127.0.0.1
# Redis服务器连接端口
spring.redis2.port=6379
# Redis服务器连接密码(默认为空)
spring.redis2.password=
# 数据库连接超时时间,2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位. 单位: 毫秒
spring.redis2.timeout=500
# 启用SSL支持。
#spring.redis.ssl=false


# 最大可用连接数(默认为8,负数表示无限)
spring.redisconfig.pool.max-active =  16
# 最大空闲连接数(默认为8,负数表示无限)
spring.redisconfig.pool.max-idle =  16
# 最小空闲连接数(默认为0,该值只有为正数才有作用)
spring.redisconfig.pool.min-idle =  8
# 连接分配在池被耗尽时抛出异常之前应该阻塞的最长时间量(以毫秒为单位)。使用负值可以无限期地阻止。 单位: 毫秒
spring.redisconfig.pool.max-wait =  100

UserModel.java内容:

package com.stamhe.springboot.model;

import java.io.Serializable;

public class UserModel implements Serializable {
	private static final long serialVersionUID = 8655851615465363473L;
    private Long id;
    private String username;
    private String password;
    
    
	public UserModel() {
		super();
	}

	public UserModel(Long id, String username, String password) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
	}
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	@Override
	public String toString() {
		return "UserModel [id=" + id + ", username=" + username + ", password=" + password + "]";
	}
}

因为要通过序列化方式存储到Redis中,所以这儿的UserModel POJO需要继承自 java.io.Serializable 。

Redis1Config.java内容:

package com.stamhe.springboot.redis;

import java.time.Duration;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;


@Configuration
public class Redis1Config {
	@Value("${spring.redis1.host}")
    private String host;
	
	@Value("${spring.redis1.port}")
    private Integer port;
	
	@Value("${spring.redis1.password}")
    private String password;
	
	@Value("${spring.redis1.database}")
    private Integer database;

	@Value("${spring.redisconfig.pool.max-active}")
    private Integer maxActive;
	
    @Value("${spring.redisconfig.pool.max-idle}")
    private Integer maxIdle;
    
    @Value("${spring.redisconfig.pool.max-wait}")
    private Long maxWait;
    
    @Value("${spring.redisconfig.pool.min-idle}")
    private Integer minIdle;
    

    /*
     * 使用springdboot操作Redis时,发现key值出现 \xac\xed\x00\x05t\x00\tb,但不影响程序读写,
     * 查询资料发现redisTemplate 默认的序列化方式为 jdkSerializeable, StringRedisTemplate的默认序列化方式为StringRedisSerializer
     * 可以通过手动配置, 将redisTemplate的序列化方式进行更改
     */
    @Bean(name="redis1Template")
    public RedisTemplate<String, Object> redis1Template(@Qualifier("lettuce1Factory")LettuceConnectionFactory lettuce1Factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuce1Factory);
        
        ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		
		GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
		
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		
		redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
		redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
		
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(name="lettuce1Factory")
    @Primary
    public LettuceConnectionFactory lettuce1Factory(RedisStandaloneConfiguration redis1RedisConfig,
            GenericObjectPoolConfig redis1PoolConfig) {
        LettuceClientConfiguration clientConfig =
                LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100))
                        .poolConfig(redis1PoolConfig).build();
        return new LettuceConnectionFactory(redis1RedisConfig, clientConfig);
    }

    
    @Bean(name="redis1PoolConfig")
    public GenericObjectPoolConfig poolConfig() {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(maxActive);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(maxWait);
        return config;
    }

    @Bean(name="redis1RedisConfig")
    public RedisStandaloneConfiguration redisConfig() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPassword(RedisPassword.of(password));
        config.setPort(port);
        config.setDatabase(database);
        return config;
    }
}

Redis1Config 演示了使用@Value直接读取配置文件参数初始化 Redis 相关连接的示例。

Redis2Config.java内容:

package com.stamhe.springboot.redis;

import java.time.Duration;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonAutoDetect;


@Configuration
@ConfigurationProperties(prefix="spring.redis2")
public class Redis2Config {
    private String host;
    private Integer port;
    private String password;
    private Integer database;

	@Value("${spring.redisconfig.pool.max-active}")
    private Integer maxActive;
	
    @Value("${spring.redisconfig.pool.max-idle}")
    private Integer maxIdle;
    
    @Value("${spring.redisconfig.pool.max-wait}")
    private Long maxWait;
    
    @Value("${spring.redisconfig.pool.min-idle}")
    private Integer minIdle;
    
    /*
     * 使用springdboot操作Redis时,发现key值出现 \xac\xed\x00\x05t\x00\tb,但不影响程序读写,
     * 查询资料发现redisTemplate 默认的序列化方式为 jdkSerializeable, StringRedisTemplate的默认序列化方式为StringRedisSerializer
     * 可以通过手动配置, 将redisTemplate的序列化方式进行更改
     */
    @Bean(name="redis2Template")
    public RedisTemplate<String, Object> redis2Template(@Qualifier("lettuce2Factory")LettuceConnectionFactory lettuce2Factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuce2Factory);

        ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		
		GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
		
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		
		redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
		redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
		
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(name="lettuce2Factory")
    public LettuceConnectionFactory lettuce2Factory(RedisStandaloneConfiguration redis2RedisConfig,
            GenericObjectPoolConfig redis2PoolConfig) {
        LettuceClientConfiguration clientConfig =
                LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100))
                        .poolConfig(redis2PoolConfig).build();
        return new LettuceConnectionFactory(redis2RedisConfig, clientConfig);
    }

    
    @Bean(name="redis2PoolConfig")
    public GenericObjectPoolConfig poolConfig() {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(maxActive);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(maxWait);
        return config;
    }

    @Bean(name="redis2RedisConfig")
    public RedisStandaloneConfiguration redisConfig() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPassword(RedisPassword.of(password));
        config.setPort(port);
        config.setDatabase(database);
        return config;
    }

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public Integer getPort() {
		return port;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Integer getDatabase() {
		return database;
	}

	public void setDatabase(Integer database) {
		this.database = database;
	}
}

Redis2Config 演示了通过 @ConfigurationProperties 结合 prefix以及getter、setter获取参数初始化 Redis 连接的示例。

HelloController.java内容:

package com.stamhe.springboot.model.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.stamhe.springboot.model.UserModel;

@Controller
@RequestMapping("/redis")
public class RedisController {
	@Autowired
	private RedisTemplate<String, Object> redis1Template;
	
	@Autowired
	private RedisTemplate<String, Object> redis2Template;

	/**
	 * http://127.0.0.1:8080/redis/ops
	 * redis的各种操作演示
	 * @return
	 */
	@RequestMapping("/ops")
	@ResponseBody
	public Map<String, Object> redisAction()
	{
		Map<String, Object> hashMap = new HashMap<String, Object>();
		hashMap.put("hello", "world");
		hashMap.put("k1", "v1");
		
		// kv
		String redisKey = "redis-k1";
		redis1Template.opsForValue().set(redisKey, "redis-v1");
		hashMap.put("redis-k1", redis1Template.opsForValue().get(redisKey));

		// hash
		String redisHashKey = "redis-hash-k1";
		redis1Template.opsForHash().put(redisHashKey, "rk1", "rv1");
		hashMap.put("redis-hash-k1", redis1Template.opsForHash().get(redisHashKey, "rk1"));
		
		// list
		String redisListKey = "redis-list-k1";
		redis1Template.opsForList().rightPush(redisListKey, "redis-list-v1");
		redis1Template.opsForList().rightPush(redisListKey, "redis-list-v2");
		redis1Template.opsForList().rightPush(redisListKey, "redis-list-v3");
		hashMap.put("redis-list-k1", redis1Template.opsForList().leftPop(redisListKey));
		Long redisListLen = redis1Template.opsForList().size(redisListKey);
		hashMap.put("redis-list-len", redisListLen.toString());
		
		// incr
		String redisIncrKey = "redis-incr-k1";
		redis1Template.opsForValue().set(redisIncrKey, 100L);
		Long redisIncrV1 = redis1Template.opsForValue().increment(redisIncrKey, 1L);
		hashMap.put("redis-incr-k1", redisIncrV1.toString());
		
		return hashMap;
	}
	
	/**
	 * http://127.0.0.1:8080/redis/object
	 * 演示了直接存储 POJO 对象的示例
	 * @return
	 */
	@RequestMapping("/object")
	@ResponseBody
	public Map<String, Object> redis2Action()
	{
		Map<String, Object> hashMap = new HashMap<String, Object>();
		
		String key = "user-1";
		redis2Template.opsForValue().set(key, new UserModel(1L, "u1", "pa"));
        final UserModel user = (UserModel) redis2Template.opsForValue().get(key);
        
        System.out.println("user = " + user);
        
        hashMap.put("1", user);
        
        return hashMap;
	}
}

这个 controller 演示了如何使用 Redis 对象操作 Redis的 kv、list、hash等数据的示例。

http://localhost:8080/redis/ops的输出:

http://localhost:8080/redis/object的输出:

《SpringBoot2从入门到工程实战》第三篇:日志系统

本章介绍SpringBoot体系下的日志使用和处理。

日志,作为一种工程师记录业务工作状态的一种数据,他是如此重要,重要到,如果一个系统,没有好的日志,我们没有办法开展解决bug、调优、跟踪逻辑等很多工作。

他是如此的重要,所以我把SpringBoot的日志系统的使用,放在第三篇来写。

SpringBoot官方是支持Logback、Log4j、Log4j2等多种日志框架的。SpringBoot默认的日志框架为Logback,而且Logback与Log4j同为一个作者开发,架构和各种理念以及各种测试,Logback都会比Log4j更强大和易用,因此我们本章的例子,也使用Logback。

但更重要的是,Logback完整实现了Log4j的接口,包名、函数名基本都兼容,如果你项目中之前使用的是Log4j框架,那么你只需要替换掉基础的jar包和相关的配置,代码完全不用管。。。

本章示例工程名称:springboot_worker_logback

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:



注:如果没有src/main/resources目录,新建一个就行了。

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_logback</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_logback</name>
  <url>http://maven.apache.org</url>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
	</properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

  	<dependencies>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
    
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	    
	    <dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-logging</artifactId>
		</dependency>
    
	    <dependency>
	    	<groupId>junit</groupId>
	      	<artifactId>junit</artifactId>
	      	<scope>test</scope>
	    </dependency>
	</dependencies>

	<!-- 
	使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
	提示了xxxxxx.jar中没有主清单属性.
	添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
	-->
	<build>
	  <plugins>
	  	<plugin>
	  		<groupId>org.springframework.boot</groupId>
	 		<artifactId>spring-boot-maven-plugin</artifactId>
	  	</plugin>
	  </plugins>
 	</build>
</project>

新引入了一个 spring-boot-starter-logging 依赖,这个代表 logback,如果想使用log4j或者log4j2,则引入 spring-boot-starter-logging-log4j或者spring-boot-starter-logging-log4j2就行了。

App.java内容:

package com.stamhe.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

跟之前一样。

HelloController.java内容:

package com.stamhe.springboot.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {
	
	private Logger logger = LoggerFactory.getLogger(getClass());

	@RequestMapping("/world")
	public String worldAction()
	{
		logger.trace("trace 日志");
		logger.debug("debug 日志");
		logger.info("info 日志");
		logger.warn("warn 日志");
		logger.error("error 日志");
		return "Hello World";
	}
}

只是多了一个Logger,包名都是org.slf4j下面的,不管你是使用logback还是log4j还是log4j2

logback-spring.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="60 seconds">
	<!-- http://www.cnblogs.com/lixuwu/p/5804793.html -->
    <!--定义日志文件的存储路径, 注意不要在 LogBack 的配置中使用相对路径-->  
    <property name="LOG_HOME" value="/data/logs/apiservice" />  
    <!-- 控制台输出 -->   
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
             <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度对齐,%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
        </encoder> 
    </appender>
    
    <!-- 按照每天生成日志文件 -->   
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/springboot_worker_logback_%d{yyyy-MM-dd}.log</FileNamePattern> 
            <!--日志文件保留天数-->
            <MaxHistory>10</MaxHistory>
        </rollingPolicy>   
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度对齐,%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
        </encoder> 
        <!--日志文件最大的大小-->
       <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
         <MaxFileSize>100MB</MaxFileSize>
       </triggeringPolicy>
    </appender> 
    
    <springProfile name="prod">
	    <logger name="org.springframework.web" level="INFO" />
	    <logger name="com.stamhe" level="INFO"/>
	    
	    <root level="INFO">
	        <appender-ref ref="STDOUT" />
	        <appender-ref ref="FILE" />
	    </root>
    </springProfile>
    
    
    <springProfile name="dev,test">
	    <logger name="org.springframework.web" level="DEBUG" />
	    <logger name="com.stamhe" level="DEBUG"/>
	    
	    <root level="DEBUG">
	        <appender-ref ref="STDOUT" />
	        <appender-ref ref="FILE" />
	    </root>
    </springProfile>
    
</configuration>

实际项目中,根据需要调整参数,不同环境的log级别,使用springProfile来进行控制。

日志配置文件名称使用logback.xml或者logback-spring.xml都行,但是使用logback-spring.xml可以使用一些SpringBoot的特定参数做控制,所以推荐使用logback-spring.xml做配置文件名称。当然,你也可以自定义参数名称,通过在application.properties中指定就行,如下:

logging.config=classpath:logging-config.xml

application.properties内容:

spring.profiles.active=dev

修改此参数,可以切换项目的工作环境,如dev、test、prod环境,因为不同环境,日志的工作模式会有不一样,可以通过这个来配置。

按我们在第二章的讲解,实际项目中,配置文件和代码仓库都是分离的,所以,很容易通过*.properties来指定不同的环境,然后打包也只需要打包一次,各个环境通用一个jar包。

启动成功后,访问: http://localhost:8080/hello/world 即可得到下面的结果:

浏览器返回:

dev环境

命令行下提示日志:




prod环境

日志文件:/data/logs/apiservice/springboot_worker_logback_2019-03-22.log

dev环境


prod环境

可以看到,通过配置不同的开发环境,日志的输出控制目的已经达到。

日志对于实际的项目来说是如此的重要,建议都要认真了解学习。

《SpringBoot2从入门到工程实战》第二篇:深入配置实战

在本章中,我会详细介绍除了SpringBoot默认加载的配置外,如何加载自定义的配置,以及按不同环境不同业务需求加载不同的配置文件。

在第一篇文章中,我们说过,SpringBoot默认加载的配置文件名称为:application*.yml,application*.yaml,application*.properties,但是从工作实战中,我们是不可能把所有的项目配置都放置在一个配置文件,也不可能把所有的配置文件都起名为application*.properties这样的形式,原因很简单:太难看、太难维护了。所以本章,我们就来学习在SpringBoot中如何使用默认的配置文件和加载自定义的配置文件。

本章示例工程名称:springboot_worker_config

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:

注:如果没有src/main/resources目录,新建一个就行了。

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_config</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_config</name>
  <url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
	</properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

  	<dependencies>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
    
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
    
	    <dependency>
	      <groupId>junit</groupId>
	      <artifactId>junit</artifactId>
	      <scope>test</scope>
	    </dependency>
	</dependencies>

	<!-- 
	使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
	提示了xxxxxx.jar中没有主清单属性.
	添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
	-->
	<build>
	  <plugins>
	  	<plugin>
	  		<groupId>org.springframework.boot</groupId>
	 		<artifactId>spring-boot-maven-plugin</artifactId>
	  	</plugin>
	  </plugins>
 	</build>
</project>

pom.xml的内容,跟第一章的内容完全一样,不再赘述。

App.java内容:

package com.stamhe.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

发现跟第一章内容还是完全一样,因为这是简单示例,基本是定式的。

CommonConfig.java内容:

package com.stamhe.springboot.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix="siteinfo")
public class CommonConfig {
	private String url;

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}
}

SiteConfig.java内容:

package com.stamhe.springboot.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:/site.properties")
@ConfigurationProperties(prefix="siteinfo")
public class SiteConfig {
	private String url1;
	private String url2;
	public String getUrl1() {
		return url1;
	}
	public void setUrl1(String url1) {
		this.url1 = url1;
	}
	public String getUrl2() {
		return url2;
	}
	public void setUrl2(String url2) {
		this.url2 = url2;
	}
}

CommonConfig和SiteConfig都是两个配置类,他们都是从配置文件中加载相关的配置参数,变化为Java Bean,供其他业务端使用。

@PropertySource可以指定加载的配置文件名称,比如你的mysql、redis、memcache配置文件,可以分别使用mysql.properties、redis.properties、memcache.properties这样的形式,这样各种业务的配置分离开,便于维护、一目了然。这种使用,我们后续在介绍整合mysql、redis、memcache的时候,都会使用到。

@PropertySource也可以指定配置加载的绝对路径以及一次加载多个配置文件,如下:

@PropertySource({
	"classpath:/memcache.properties", 
	"file:/opt/conf/mysql.properties", 
	"file:/opt/conf/redis.properties"
})

注意:

这种加载是有顺序的,后面key,会覆盖掉前面的同名key的值。

@ConfigurationProperties(prefix=”siteinfo”) 注解的使用,是用来标识当前类里面的属性,都使用前面为prefix指定的前缀做拼接,达到简化代码的目的。当然了,我们还是可以继续用传统的@Value指定的方式,如下:

@Value("${siteinfo.url1}")
private String url1;

@Value("${siteinfo.url2}")
private String url2;

HelloController.java内容:

package com.stamhe.springboot.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.stamhe.springboot.config.CommonConfig;
import com.stamhe.springboot.config.SiteConfig;

@RestController
@RequestMapping("/hello")
public class HelloController {
	
	@Autowired
	private SiteConfig siteConfig;
	
	@Autowired
	private CommonConfig commonConfig;

	@RequestMapping("/world")
	public String worldAction()
	{
		return commonConfig.getUrl();
	}
	
	@RequestMapping("/list")
	public Map<String, String> listAction()
	{
		HashMap<String, String> map = new HashMap<>();
		map.put("url1", siteConfig.getUrl1());
		map.put("url2", siteConfig.getUrl2());
		
		return map;
	}
}

基本跟第一章的内容一样,只是新增了一个函数,返回Map数据,其实最终就是返回json数据了。

application.properties内容:

siteinfo.url=https://www.google.com

site.properties内容:

siteinfo.url1=http://www.stamhe.com
siteinfo.url2=https://www.qq.com

各种代码和配置准备完毕,启动起来吧。

访问:http://localhost:8080/hello/world 你会得到:

https://www.google.com

访问 http://localhost:8080/hello/list 你会得到:

{"url1":"http://www.stamhe.com","url2":"https://www.qq.com"}

怎么样?是不是很简单?好了,简单的介绍完了,下面我们说点儿不一样的。

一般的开发流程,都是会有开发环境、生产环境,多数还会有测试环境、灰度环境等很多不同的环境,SpringBoot为我们提供了 spring.profiles.active=dev参数来指定环境(在application.properties中指定或者java -jar xxx.jar –spring.profiles.active=dev指定),同时不同的工作环境,会加载不同的配置文件,如你指定 spring.profiles.active=dev,则会加载application-dev.properties,如你指定spring.profiles.active=test,则会加载application-test.properties。这种对于这种简单的场景开发,都是有用的,比如用来放置不同的数据库连接信息、不同的redis连接信息、不同的回调url。很多文章、博客、甚至很多工程实践,都是这么说、这么做的。

但是,对于大型工程项目来说,这样是不对的,主要有以下几点原因:

  • 安全性无法保障,我们都知道,如果直接把配置项放置在src/main/resources目录下,这样每个有代码权限的人,都可以拿到配置文件,各种数据库的连接信息,秘钥文件,一览无余,这个在大型工程项目中,根本就不应该这样做,也不应该允许这么做。
  • 不管是Java这种静态编译型语言,还是PHP这种动态脚本语言,从项目的工程实践上说,业务代码逻辑,应该是脱离配置文件工作的、应该是无状态的,这样方便打包、方便发布、方便备份、方便回滚。
  • application-dev.properties、application-test.properties的使用,又回到了我们前面说的,这种配置太冗余了、太难看了、太难维护了,不是项目的初始化开发者,压根不知道应该直接去哪个配置文件找配置项,只能挨个找,不是一种对的工程实践。

综上,这也是这一系列文章和其他教程的本质区别,我更趋向于用一种实际工程的实战来写这个系列文章。

那么,这些问题怎么解决呢?

答案就是使用绝对路径的统一配置文件名,如前面的例子:

@PropertySource({
	"file:/opt/conf/mysql.properties", 
	"file:/opt/conf/redis.properties"
})

这种配置模式,你会发现,在各个不同环境的服务器,只需要配置好不同的配置文件,那么,打包好的jar包,线上线下各个环境,都可以用同一个,压根不用关注环境问题,也满足安全性要求,更方便维护和让配置文件的结构清晰。

这些,我们在后面的系列文章中,都会有详细的体现。

《SpringBoot2从入门到工程实战》第一篇:Hello World

技术博客,开篇不能免俗,从Hello World开始吧.

新建一个Maven的quickstart工程,配置Java版本为 1.8.x版本.

本章示例工程名称:springboot_worker_helloworld

代码地址:https://github.com/stamhe/SpringBoot-Work-Example

目录结构如下:

pom.xml内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stamhe</groupId>
  <artifactId>springboot_worker_helloworld</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_worker_helloworld</name>
  <url>http://maven.apache.org</url>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
	</properties>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

  	<dependencies>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
    
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
    
	    <dependency>
	      <groupId>junit</groupId>
	      <artifactId>junit</artifactId>
	      <scope>test</scope>
	    </dependency>
	</dependencies>

	<!-- 
	使用Spring Boot微服务搭建框架,在eclipse和Idea下能正常运行,但是在打成jar包部署或者直接使用java -jar命令的时候,
	提示了xxxxxx.jar中没有主清单属性.
	添加 spring-boot-maven-plugin然后再执行mvn install 或者 mvn clean package 即可解决.
	-->
	<build>
	  <plugins>
	  	<plugin>
	  		<groupId>org.springframework.boot</groupId>
	 		<artifactId>spring-boot-maven-plugin</artifactId>
	  	</plugin>
	  </plugins>
 	</build>
</project>



pom.xml说明:

  • <parent>项是SpringBoot的父级依赖,这个标识当前的项目就是SpringBoot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,其他常用的官方starter包就可以省去version标签了,这样也统一了Maven依赖包的版本。
  • spring-boot-starter :核心模块,包含有自动配置支持、日志、application.yml以及application.properties等。如果引入了 spring-boot-starter-web web 模块可以去掉此配置,因为 spring-boot-starter-web 自动依赖了 spring-boot-starter。
  • spring-boot-starter-test :测试模块,包括 JUnit、Mockito。
  • <build>项主要是用来提供将SpringBoot打包为jar包时,使用java -jar xxx.jar 方式运行的打包支持

App.java内容:

package com.stamhe.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

App启动类说明:

  • 主函数入口
  • 使用了@SpringBootApplication来注解类,点击进入SpringBootApplication源码可知,改注解主要是:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan等注解的集合,通常我们说SpringBoot整合了Spring、SpringMVC简化了开发,这种类似的注解集合,就是提现之一,相信之前用SpringMVC开发过项目的同学深有体会,后面还有@RestController也是一样的注解集合。
  • 使用SpringApplication.run来加载入口类

@SpringBootApplication注解以及SpringApplication.run()的使用,都是SpringBoot项目的标准性动作,每一个SpringBoot项目都必须的。

HelloController.java内容:

package com.stamhe.springboot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {

	@RequestMapping("/world")
	public String worldAction()
	{
		return "Hello World";
	}
}

Controller类说明:

@RestController这个注解,其实是@Controller,@RequestMapping,@ResponseBody 这三个注解的集合,点开源码,我们就知道了,这个是标准的SpringMVC注解。基本作用如下:

  • Controller:标识此类为一个控制器,Spring会自动实例化此类。
  • RequestMapping:URL映射。
  • ResponseBody:将返回结果自动转换为json串。后面的章节会有详细的演示。

修改完毕后,在Eclipse或者IDEA里面启动Application(或者使用mvn clean package后,在项目目录下执行 java -jar target/springboot_worker_helloworld-0.0.1-SNAPSHOT.jar 来启动),启动成功后,在浏览器或者命令行下面,访问

http://localhost:8080/hello/world

即可得到下面的信息:




SpringBoot项目解析:

  • 默认情况下将/src/main/resources目录下的文件作为资源文件加入到classpath下,只对application*.yml,application*.yaml,application*.properties 这样格式的文件进行匹配过滤。配置文件的使用,我们会在下一章节专门讲解。
  • SpringBoot使用约定的配置文件、标准的注解集合以及统一的项目启动方式,简化了很多之前SpringMVC开发项目繁琐配置,带来了非常强烈的开发体验提升,基本算得上是革命性的改变。
  • SpringBoot默认带有一个内置的tomcat,其参数也可以自定义,性能也足以跟原生的tomcat媲美,完全可以使用它来做项目的容器,这样可以直接使用java -jar xxx.jar的方式来运行项目。

下一章,我们将深度解析SpringBoot的配置文件的使用。

《SpringBoot2从入门到工程实战》系列开篇

最近在为团队业务做微服务的系统化改造做架构设计,引入SpringBoot做底层的服务框架,对上层的PHP提供HTTP接口,逐步移除掉PHP层跟数据层的接触,达到PHP层完全只有业务组装调用的目的。

作为一个系统性的工作总结,计划写一系列的SpringBoot的文章,来对SpringBoot在工程中的使用做一个系统系的总结和介绍。

也看了很多第三方的博客,总感觉写的不够好,特别很多只有很简单的入门,没有面向大型业务系统的工程实战,如缺少分库分表,大规模缓存的使用等等。

这个系列博客,基于SpringBoot 2.x版本,Java 1.8.x版本,Maven+Eclipse.

一种通用的百亿级数据清洗方案

最近两个月,一直在和刀哥两个人重构连尚读书这边的用户行为日志系统。到本周放量完所有的数据到新系统,基本算告一个段落。

目前的业务情况,大概是有新旧两套日志系统数据,日志规模(2018.11),新日志一天产生6000万条日志,旧日志一天产生4000万条日志数据,合计1亿条上报日志,这些日志数据,都是合并上报的,根据统计,大概一条上报日志,会产生50条最终日志数据。所以最终的日志量,超过了50亿条(写这篇博客时,2019.1,已经超过70亿条)。

根据业务特点,预计的高峰期是10个小时(按40000秒计算), 100000000 / 40000 = 2500条/秒。整个系统每秒要完成 2500条上报日志的收集。然后要转化为 2500 * 50 = 125000条/秒的最终结果日志数据。晚上21 ~ 22点的最高峰,处理量还要预留x4,估计会处理10000条的收集日志数据,完成生产500000条/秒最终结果日志数据。

最终结果日志数据文件的单条大小在1 ~ 2 kbyte之间,多数为1 kbyte

最终结果日志数据,需要存储在本地硬盘+大数据的Kafka集群(9台机器)两边做容灾。

如此大规模的数据,不管是收集,清洗,本地存储,还是入库,都是非常有挑战的一项工作,更何况是重构。。。

一、旧的架构情况,使用的机器规模大概是20台机器.

注释:

1、Bear服务是这边之前的同事用Golang + Leveldb写的一个服务,可以用来接收HTTP请求传输的数据到Leveldb,然后再把对应的数据,发送到指定的HTTP 地址(转发数据)或者是发送到指定的Kafka集群,可以起到数据的缓存作用

2、之前得到的同事的反馈,都说这个Bear服务的性能很高,不存在问题

旧架构存在的问题

1、无法追踪单条数据的处理情况,因为数据没有任何的标号,导致要反查或者是顺查数据,基本没法

2、Bear只会把数据存储在Leveldb里面,外层没有日志,再加上没有源码,无法做任何修改(我也不知道为啥没源码啊)

3、之前的同事反馈说Bear的性能很高,不存在性能问题,但是我们实际观察发现,问题最大的就是这个Bear,一个是接收性能不行,另外一个就是数据转发性能不行,不管是接收HTTP数据,还是转发数据到HTTP或者Kafka集群,Bear都给不出来量,所以导致经常出现拥堵的情况,需要不停的多开Bear服务,其性能有严重的问题

4、本地没有数据,无法做数据重放。不管是收集日志,还是最终日志,我们这边完全没任何原始数据,一旦入了Kafka集群后,我们就无能为力了,无法校对数据,无法重放,无法追踪处理流程

5、因为使用Bear做数据缓存,他只支持HTTP请求的数据清洗,导致只能走php-fpm,然后他的转发性能又不行,所以导致整个系统的数据清洗能力极低

二、新的架构情况,使用的机器规模为12台 + 3 组Redis实例

优点

1、不管是收集的原始日志数据还是处理以后的最终日志数据,我们都有存储,随时可以重放

2、每条日志加了不会重复的logid,可以根据logid追踪到任何一条数据的流转过程和处理结果,并且大数据那边可以根据logid去重数据

3、不管是收集端的API日志服务器,还是Redis,还是消费服务器,都是可以任意的横向的扩展的,而且不影响任何第三方,性能可以直线上升

4、日志数据压缩,采用的是pigz的并行压缩,每天凌晨压缩前一天的日志数据文件,在目前的日志规模下,在一个小时内,可以压缩完成所有的日志,对业务和机器负载不会带来任何影响,而且数据的压缩比率,在1:20以上

5、因为采用了脚本主动拉取的模式,所以只要消费服务器足够,理论上任何数据都会在3s内被处理,达到实时效果

6、业务采用了脚本的模式,执行效率相比原来的Nginx + php-fpm的模式,性能提升了几十倍,而且因为不走HTTP协议了,所以内网的带宽使用也成倍的下降,不到原来的一半

7、因为使用了Logstash的批量提交模式,数据入Kafka的性能,提升了几十倍

8、因为启用了Logstash的永久队列,基本上保障了数据的不丢失,经过对比,和Kafka里面统计出来的数据,误差在百万分之几的级别

9、在入Logstash时,采用了失败就扔Redis队列再用另外的消费脚本入可用的其他Logstash的方式,保障说即使本地的Logstash宕机,数据依然可以正常的入Kafka集群

10、消费脚本进程,采用了supervisor来做管理,新、旧版消费日志脚本,单机各开100个脚本,主要 supervisor 需要采用 3.3以上的版本,3.3以下的版本,对于管理几百个进程,会有问题

11、各段都可以方便的知道性能瓶颈,不管是Filebeat的收集端,Redis队列端,还是Logstash端,都提供了良好的监控数据,可以即时了解系统的工作状态。

三、重构完成后,新旧系统的服务器机器负载对比

旧系统单机

新系统

消费服务器的负载
Redis负载

目前,单日清洗的数据量,已经超过70亿条,按现在的机器负载预估,这5台机器,预计处理清洗150亿条日志数据,没有任何问题。

优化Filebeat+Logstash的性能

最近一直在重构新公司的日志传输系统,因为业务量较大,目前的单日日志条数已经超过了30亿条,而且业务有明显的高峰期。所以日志收集传输系统,必须要满足明显的波峰性能要求。方案还是以Filebeat + Logstash为主,Logstash直接入kafka, Filebeat从磁盘读取文本文件(json格式)。

优化完成后,单filebeat + 单logstash可以处理 30000条/秒的日志. 单filebeat + 多logstash可以处理 40000条/秒的日志.

日志大小为1.2kbyte.

环境信息:

2台服务器, 32core, 200GB内存, SSD硬盘, 千兆网络内网互联

Filebeat 6.5.2

/usr/bin/filebeat -c /data/filebeat.yml

Logstash 6.5.2

/usr/share/logstash/bin/logstash -f /etc/logstash/logstash-file.conf --path.settings /etc/logstash --path.data /data/tmp/logstash/data --path.logs /data/tmp/logstash/logs -w 8

优化后的配置文件信息如下:

filebeat.yml

关键参数:

scan_frequency

harvester_buffer_size

queue.mem.*

filebeat.inputs:
- type: log

  # Change to true to enable this input configuration.
  enabled: true
  encoding: utf-8

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /data/tmp/data/testlogstash3.log
    #- c:\programdata\elasticsearch\logs\*

# Filebeat以多快的频率去prospector指定的目录下面检测文件更新比如是否有新增文件如果设置为0s则Filebeat会尽可能快地感知更新占用的CPU会变高。默认是10s。
  scan_frequency: 1s
  # 如果设置为true, Filebeat从文件尾开始监控文件新增内容把新增的每一行文件作为一个事件依次发送而不是从文件开始处重新发送所有内容。
  tail_files: false
  harvester_buffer_size: 104857600

# backoff选项指定Filebeat如何积极地抓取新文件进行更新。默认1s. backoff选项定义Filebeat在达到EOF之后再次检查文件之间等待的时间
  backoff: 1s
# 在达到EOF之后再次检查文件之前Filebeat等待的最长时间
  max_backoff: 10s

  close_inactive: 6h
  clean_inactive: 72h
  ignore_older: 70h
  close_timeout: 6h
  fields:
    log_name: newdata

- type: log

  # Change to true to enable this input configuration.
  enabled: true
  encoding: utf-8

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /data/topicdata/*.hequandata

# Filebeat以多快的频率去prospector指定的目录下面检测文件更新比如是否有新增文件如果设置为0s则Filebeat会尽可能快地感知更新占用的CPU会变高。默认是10s。
  scan_frequency: 1s
  # 如果设置为true, Filebeat从文件尾开始监控文件新增内容把新增的每一行文件作为一个事件依次发送而不是从文件开始处重新发送所有内容。
  tail_files: false

# backoff选项指定Filebeat如何积极地抓取新文件进行更新。默认1s. backoff选项定义Filebeat在达到EOF之后再次检查文件之间等待的时间
  backoff: 1s
# 在达到EOF之后再次检查文件之前Filebeat等待的最长时间
  max_backoff: 10s

  close_inactive: 6h
  clean_inactive: 72h
  ignore_older: 70h
  close_timeout: 6h
  fields:
    log_name: olddata



#============================= Filebeat modules ===============================

filebeat.config.modules:
  # Glob pattern for configuration loading
  path: ${path.config}/modules.d/*.yml

  # Set to true to enable config reloading
  reload.enabled: false

#==================== Elasticsearch template setting ==========================

setup.template.settings:
  index.number_of_shards: 3
  #index.codec: best_compression
  #_source.enabled: false

#================================ General =====================================
filebeat.registry_file: /data/tmp/registry-logstash3.index

max_procs: 8

path.home: /etc/filebeat
path.config: /etc/filebeat
path.data: /data/filebeat/data
path.logs: /data/filebeat/logs

queue.mem.events: 409600
queue.mem.flush.min_events: 1024
queue.mem.flush.timeout: 2s


#================================ Outputs =====================================
output.logstash:
  enable: true
  hosts: ['10.28.3.8:5046','10.28.3.8:5047','10.28.3.8:5048','10.28.3.8:5049']
  loadbalance: true
  worker: 2
  bulk_max_size: 50000
  compression_level: 0


#================================ Logging =====================================

# Minimum log level. One of debug, info, warning, or error. The default log level is info
logging.level: info
logging.to_files: true
logging.files:
  path: /data/tmp
  name: filebeat-logstash3.log
  keepfiles: 7
  rotateeverybytes: 10485760
  interval: 24h
  permissions: 0644

logstash.conf配置文件

input {
    beats {
        port => 5046
    }
}

filter {
    grok {
        match => {
            "message" => "(?<kafkaname>\S+?)##########(?<kafkadata>.*)"
        }
        overwrite => ["message"]
    }
}

output {
    if[fields][log_name] == "newdata" or [fields][log_name] == "olddata" {
        file {
            path => "/data/tmp/logstash/logstash-kafkadata5046.log"
                codec => line {
                    format => "%{+YYYY-MM-dd HH:mm:ss}##########%{kafkadata}"
                }
        }
    }
}

logstash.yml配置文件

pipeline.workers: 15
pipeline.output.workers: 15
pipeline.batch.size: 5000
pipeline.batch.delay: 10

优化后的结果(单Filebeat + 单Logstash):

单条文本大小为 1.2kbyte

优化后的结果(单Filebeat + 4个Logstash):

单条文本大小为 1.2kbyte大小.

在Ubuntu 16.04 以及Windows下面编译生成门罗币(XMR)挖矿程序(xmr-stak)

门罗币作为2014年诞生的真正匿名性加密货币,其潜力巨大,挖矿难度也巨大,批量部署自己的矿机就涉及到编译自己的挖矿程序。
平台
ubuntu 16.04/14.04

apt-get install -y build-essential libtool autotools-dev autoconf pkg-config libssl-dev  git-core libboost-all-dev libqrencode-dev libminiupnpc-dev libevent-dev libsodium-dev build-essential libtool autotools-dev autoconf automake libssl-dev libboost-all-dev libdb-dev libdb++-dev pkg-config libevent-dev git-core cmake libmicrohttpd-dev libssl-dev cmake build-essential libhwloc-dev
https://github.com/fireice-uk/xmr-stak.git
cd xmr-stak  # 可以找到里面donate的地方,将算力捐赠调整为0%
mkdir build
cd build
cmake .. -DCUDA_ENABLE=OFF -DCPU_ENABLE=ON -DOpenCL_ENABLE=ON  # 只支持CPU&AMD
cmake .. -DCUDA_ENABLE=ON -DCPU_ENABLE=ON -DOpenCL_ENABLE=OFF # 只支持CPU&NVIDIA
cmake .. -DCUDA_ENABLE=OFF -DCPU_ENABLE=ON -DOpenCL_ENABLE=OFF # 只支持CPU

因为自己手边都是nvidia的显卡矿机,所以下面添加部署Nvidia CUDA支持的指令步骤
https://developer.nvidia.com/cuda-downloads
在上面的网页里面,有nvidia官方的各平台编译说明
因为我自己是ubuntu 16.04的平台,所以下面的步骤针对ubuntu 16.04平台

https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1604&target_type=debnetwork
wget -c "http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_9.1.85-1_amd64.deb"
dpkg -i cuda-repo-ubuntu1604_9.1.85-1_amd64.deb
apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
apt-get update
apt-get install cuda
ldconfig

平台
Windows x64

https://developer.nvidia.com/compute/cuda/9.1/Prod/local_installers/cuda_9.1.85_windows


cd xmr-stak

"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\VsMSBuildCmd.bat"
set CMAKE_PREFIX_PATH=C:\xmr-stak-dep\hwloc;C:\xmr-stak-dep\libmicrohttpd;C:\xmr-stak-dep\openssl

set CUDA_TOOLKIT_ROOT_DIR=C:\cuda91\dev
set CUDA_NVCC_EXECUTABLE=C:\cuda91\dev\bin
set CUdA_INCLUDE_DIRS=C:\cuda91\dev\include
set CUDA_CUDART_LIBRARY=C:\cuda91\dev\lib
mkdir build
cd build

cuda8 => unknown

cuda9
cmake -G "Visual Studio 15 2017 Win64" -T v141,host=x64 -DCUDA_TOOLKIT_ROOT_DIR=C:/cuda91/dev -DOpenCL_ENABLE=OFF ..
cmake -G "Visual Studio 14 2015 Win64" -T v140,host=x64 -DCUDA_TOOLKIT_ROOT_DIR=C:/cuda91/dev -DOpenCL_ENABLE=OFF ..

cmake --build . --config Release --target install


Visual Studio 2017 Community
blob:https://www.visualstudio.com/26d7f816-38d5-4ace-a55d-249bd2f056d4