pinpoint 告警配置

配置页面

1.点击配置按钮

图片1

2。添加user 到userGroup

图片2

图片5

注意:user Group Add的时候界面有个小bug,Add按钮disable,需要在搜索框搜索某个userGroup比如中间件,然后点击Add按钮,方可添加

3.添加告警配置

Pinpoint-web周期性的检查应用的状态,如果特定前置条件(规则)满足时则触发告警。

这些条件(默认)每3分钟被web模块中的后台批处理程序检查一次,使用最后5分钟的数据。一旦条件满足,批处理程序发送短信/邮件给注册到用户组的用户。

图片3

图片4

  • SLOW COUNT / 慢请求数当应用发出的慢请求数量超过配置阈值时触发。
  • SLOW RATE / 慢请求比例当应用发出的慢请求百分比超过配置阈值时触发。
  • ERROR COUNT / 请求失败数当应用发出的失败请求数量超过配置阈值时触发。
  • ERROR RATE / 请求失败率当应用发出的失败请求百分比超过配置阈值时触发。
  • TOTAL COUNT / 总数量当应用发出的所有请求数量超过配置阈值时触发。

    以上规则中,请求是当前应用发送出去的,当前应用是请求的发起者。 以下规则中,请求是发送给当前应用的,当前应用是请求的接收者。

  • SLOW COUNT TO CALLEE / 被调用的慢请求数量当发送给应用的慢请求数量超过配置阈值时触发。
  • SLOW RATE TO CALLEE / 被调用的慢请求比例当发送给应用的慢请求百分比超过配置阈值时触发。
  • ERROR COUNT TO CALLEE / 被调用的请求错误数当发送给应用的请求失败数量超过配置阈值时触发。
  • ERROR RATE TO CALLEE / 被调用的请求错误率当发送给应用的请求失败百分比超过配置阈值时触发。
  • TOTAL COUNT TO CALLEE / 被调用的总数量当发送给应用的所有请求数量超过配置阈值时触发。

    下面两条规则和请求无关,只涉及到应用的状态

  • HEAP USAGE RATE / 堆内存使用率当应用的堆内存使用率超过配置阈值时触发。
  • JVM CPU USAGE RATE / JVM CPU使用率当应用的CPU使用率超过配置阈值时触发。

阿里代码规范插件-来自阿里的神秘力量

hello 大家好~

作为程序猿,深知代码规范的重要性,其好处就不用说了,易读,易维护,代码跑起来也有效率、爽快。

不管是使用什么IDE,其自身都会集成一些规范检查,不过基本都是纯英文的,我相信大多人都不怎么会去看的,说句打脸的话,我也不怎么看,,手动捂脸。。

最近阿里的大神们推出了一款代码规范检查插件,试用了下,效果不错,今个做一简单推荐介绍。

GITHUB:https://github.com/alibaba/p3c

官方说明:https://mp.weixin.qq.com/s/IbibsXlWHlM59kfXJqRvZA#rd

首先,怎么安装,so easy~

打开我们的Idea ,选择 File – Settings – Plugins – Browse repositories

然后搜索 Alibaba

安装

搜索出来的第一个插件就是,然后在右边红框会有安装按钮,我这个已经是装过了,装完以后重启Idea即可。

然后,怎么使用嘞。

使用1

可以在类、包以及整个项目上使用,点击右键,就会看到红框位置有一个编码规约扫描,是它是它就是它,大胆的点击它,然后就是见证奇迹的时刻。

使用3 使用2

我这个是跑了整个项目,它的检查结果分为三个级别:简单理解就是崩溃>严重>重要,咱们优先处理等级高的问题,比如下面这个

使用3

点进来以后详细的代码位置都有,然后有没有注意到上边有个神奇的按钮,为代码添加大括号!别犹豫,点它!瞬间当前类下所有没加大括号的全有了有没有,神不神奇,厉不厉害

使用4

这简直就是真·良心功能啊。

另外,它还有一个实时检查的功能,代码有波浪线的时候,鼠标放上去,就会有提示了。

这款强大的插件还有更多神奇的功能,我也刚刚开始使用,还需要大家一起去发现,去研究,插件终究只是辅助

心中有规范、处处成规范

什么时候我们写出的代码,任何主流规范检查都检查不出大问题,岂不是一大快事~

少年,还等什么,快试试吧!

mysql分表分页

之前涛哥建议用代码拼接的方式去分表分页,而不是sql拼接(不要union,也不要list分页),我就这么做了,贴一下代码分享下,有点乱,有更好的建议欢迎提出:
详细代码参考(axxBank)com.aixuexi.axxBank.dao.IncomeRecordDetailDao#getActualAccountInfoByCondition

List<IncomeRecordDetailInfo> list = new ArrayList<IncomeRecordDetailInfo>();
DateFormat df = new SimpleDateFormat(DateConstant.FORMAT_PATTERN_YMD1);
List<String> dateList = FinaicialCommon.getDateArrayByDate(actualAccountParams.getStartDate(), actualAccountParams.getEndDate());//对接入的起始日期作处理,决定接下来要查哪些表
int startIndex = (pageBean.getPageNum() – 1) * pageBean. getPagesize();//分页的开始位置
int endIndex = pageBean.getPageNum() * pageBean.getPagesize();//分页的结束位置
int sum = 0,start,end;//sum(记录开始到当前的总结果数) start(limit的开始位置) end(limit的大小)
Map<String, Integer> mapCount = getIncomeCountList(actualAccountParams, dictionaryIds, dateList, df);//获取每个分表对应的结果数 以及总结果数 (key:dateString value:各分表的结果数)
for (int date = dateList.size() – 1; date >= 0; date –) {//默认按时间倒序
String dateString = dateList.get(date);
String tableIncomeString = new StringBuilder(“income_record_”).append(dateString).toString();//分表名
String tableAmountString = new StringBuilder(“amount_record_”).append(dateString).toString();//分表名
StringBuilder sb = 。。。。//对分表的sql查询,我就不贴代码了,可以到对应的代码处查看
if(isPage) {//是否需要分页()
int resultCount = mapCount.get(dateString) == null ? 0 : mapCount.get(dateString);//每次(每个分表)查询的结果数
/**以下是分表分页算法,用于拼接分表之后的分页查询结果**/
sum += resultCount;//记录当前的结果总数,用于分页拼接
if (sum >= startIndex) {//sum 当前的结果总数 startIndex 分页的开始位置
if (sum > endIndex) {//当前结果总数是否大于分页的结束位置(当前表已经满足分页请求了)
start = startIndex – (sum – resultCount) < 0 ? 0 : (startIndex – (sum – resultCount)); //计算查询当前表的开始位置 如果为负置为0
end = startIndex – (sum – resultCount) + pageBean.getPagesize();//查询当前表的结束位置
sb.append(” LIMIT “).append(start).append(“,”).append(end-start);//对当前表指定分页,分页大小直接pageSize也行
list.addAll(jdbcTemplate.query(sb.toString(), new IncomeRecordDetailMapper()));
break;
} else if (sum <= endIndex) {//当前结果总数小于 分页的结束位置(当前表不满足分页请求,拼接完当前表,则继续拼接下一个表)
start = (startIndex – (sum – resultCount)) < 0 ? 0 : (startIndex – (sum – resultCount));//当前表的开始位置 如果为负置0
end = resultCount; //当前表的结束位置,结束位置直接是当前表的总结果数
sb.append(” LIMIT “).append(start).append(“,”).append(end);//当前表指定分页
list.addAll(jdbcTemplate.query(sb.toString(), new IncomeRecordDetailMapper()));
}
}
}else {//不需要分页的话直接拼接所有结果
list.addAll(jdbcTemplate.query(sb.toString(),new IncomeRecordDetailMapper()));
}
}

微服务使用logback+打成tar包方式

hello,all

为推行微服务统一部署管理,需要将所有微服务项目打成tar包的形式。

同时,承祝强力推荐logback,因此也同时去掉了log4j,集成logback(性能好呀)。

下面简要介绍一下要点,注意摆正姿势:

参考项目:revolver,分支:assembly_new

1.打tar包

  • 在pom.xml里引入maven-assembly-plugin插件
<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.0.0</version>
    <configuration>
        <descriptors>
            <descriptor>src/main/assembly.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  • 在src/main目录里添加assembly.xml(assembly的配置文件),内容如下:
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
   <id>assembly</id>
   <formats>
      <format>tar.gz</format>
   </formats>
   <includeBaseDirectory>true</includeBaseDirectory>

   <fileSets>
      <fileSet>
         <directory>bin</directory>
         <outputDirectory>bin</outputDirectory>
         <fileMode>0755</fileMode>
         <filtered>false</filtered>
      </fileSet>
      <fileSet>
         <directory>src/main/resources</directory>
         <outputDirectory>conf</outputDirectory>
         <fileMode>0644</fileMode>
         <filtered>false</filtered>
      </fileSet>
      <fileSet>
         <directory>${resource.path}</directory>
         <outputDirectory>conf</outputDirectory>
         <fileMode>0644</fileMode>
         <filtered>false</filtered>
      </fileSet>
   </fileSets>
   <dependencySets>
      <dependencySet>
         <outputDirectory>lib</outputDirectory>
      </dependencySet>
   </dependencySets>
</assembly>
  • 在项目的根目录(与src平级)添加bin目录,里面放四个脚本:dump.shserver.shstart.shstop.sh。脚本太多,这里就不粘贴出来了,可以到参考项目里取。拷贝脚本到自己项目之后需要修改start.sh里的
START_CLASS=启动类全限定名

启动类如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    private static Logger logger = LoggerFactory.getLogger(Main.class);

    private static final String CONFIG_FILE_SPRING = "spring.xml";
    private static volatile boolean running = true;
    private static ClassPathXmlApplicationContext context = null;

    public static void main(String[] args) {
        try {
            long before = System.currentTimeMillis();
            start();
            long time=System.currentTimeMillis()-before;
            String logLine = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " dubbox server started! it takes "+time+" ms.";
            System.out.println(logLine);
            logger.info(logLine);
        } catch (RuntimeException e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            System.exit(1);
        }

        if(!"false".equals(System.getProperty("dubbo.shutdown.hook"))) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    context.stop();
                    Class t = Main.class;
                    synchronized(Main.class) {
                        running = false;
                        Main.class.notify();
                    }
                }
            });
        }

        synchronized (Main.class) {
            while (true) {
                try {
                    Main.class.wait();
                } catch (Throwable e) {
                }
            }
        }
    }

    private static void start() {
        context = new ClassPathXmlApplicationContext(new String[] { CONFIG_FILE_SPRING });
        context.start();
    }
}

注:脚本取自中间件组新项目里的bin目录下的脚本,然后加以修改。并且摒弃了原来deploy目录里的脚本。注意不要混合使用

  • 打了tar包之后,在线下jenkins部署的脚本如下
#!/bin/bash
echo "kill PID!!"
BUILD_ID=DONTKILLME
cd $WORKSPACE/target
echo "kill PID!"
ps -ef|grep $JOB_NAME|awk {'print $2'}|xargs kill -9
sleep 2
tar -zxvf $JOB_NAME-assembly.tar.gz
cd $JOB_NAME/bin
sh ./start.sh
sleep 2

2.引入logback

  • 在pom文件里引入logback依赖,1.2.3为logback目前最新版本。
<logback.version>1.2.3</logback.version>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>
  • 引入log4j-over-slf4j,并且在dubbo依赖里排除log4j。原因是dubbo默认使用lo4fj打印日志,而使用logback之后还想dubbo打印日志,需要如此配置。(SLF4J ship with a module called log4j-over-slf4j. It allows log4j users to migrate existing applications to SLF4J without changing a single line of code but simply by replacing the log4j.jar file with log4j-over-slf4j.jar, as described below.)
<slf4j.version>1.7.25</slf4j.version>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.8.3</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 在resources_xxx里引入logback.xml,并且修改logDir的值为日志根目录,程序启动后会在日志根目录下创建四个文件夹debug、info、warn、error。不同级别的日志打到不同的文件夹里,并且按天滚动。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志文件 输出位置 -->
    <property name="logDir" value="/data/var/log/application/Revolver" />

    <property name="maxHistory" value="30" />

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- ERROR级别日志 -->
    <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/error/error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/error/error.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- WARN级别日志 appender -->
    <appender name="warnFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/warn/warn.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/warn/warn.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- INFO级别日志 appender -->
    <appender name="infoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/info/info.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/info/info.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- DEBUG级别日志 appender -->
    <appender name="debugFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/debug/debug.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/debug/debug.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <root>
        <level value="info"/>
        <!-- 控制台输出 -->
        <appender-ref ref="console"/>
        <!-- 文件输出 -->
        <appender-ref ref="errorFile"/>
        <appender-ref ref="warnFile"/>
        <appender-ref ref="infoFile"/>
        <appender-ref ref="debugFile"/>
    </root>
</configuration>

以上

by the way:markdown写起来真的很舒服

基于大数据的用户画像

什么是用户画像?
简而言之,用户画像是根据用户社会属性、生活习惯和行为等信息而抽象出的一个标签化的用户模型。构建用户画像的核心工作即是给用户贴“标签”,而标签是通过对用户信息分析而来的高度精炼的特征标识。用户画像作为“大数据”的核心组成部分,在众多互联网公司中一直有其独特的地位。
举例来说,如果你经常购买一些玩偶玩具,那么电商网站即可根据玩具购买的情况替你打上标签“有孩子”,甚至还可以判断出你孩子大概的年龄,贴上“有5-10岁的孩子”这样更为具体的标签,而这些所有给你贴的标签统在一次,就成了你的用户画像,因此,也可以说用户画像就是判断一个人是什么样的人。

用户画像的作用
精准营销,分析产品潜在用户,针对特定群体利用短信邮件等方式进行营销;
用户统计,比如中国大学购买书籍人数 TOP10,全国分城市奶爸指数;
数据挖掘,构建智能推荐系统;
进行效果评估,完善产品运营,提升服务质量,其实这也就相当于市场调研、用户调研,迅速下定位服务群体,提供高水平的服务

数据收集
数据收集大致分为网络行为数据、服务内行为数据、用户内容偏好数据、用户交易数据这四类。

  • 网络行为数据:活跃人数、页面浏览量、访问时长、激活率、外部触点、社交数据等
  • 服务内行为数据:浏览路径、页面停留时间、访问深度、唯一页面浏览次数等
  • 用户内容便好数据:浏览/收藏内容、评论内容、互动内容、生活形态偏好、品牌偏好等
  • 用户交易数据(交易类服务):贡献率、客单价、连带率、回头率、流失率等

用户画像的系统架构:

2-9-880x450

美团架构

523

携程架构

上图是美团的和携程的系统架构,不知道是几年前的了。但是从两个架构里可以看出共同的部分:采集、计算、存储/查询和监控。采集的数据分为非实时和实时两种。数据的准确性是衡量用户画像价值的关键指标。

所以,我们现在要考虑的问题是:

我们为什么要给教师做画像?

我们怎么给教师做画像?

我们怎么评估我们给教师做的画像?

Java的Fork/Join任务,你写对了吗?

当我们需要执行大量的小任务时,有经验的Java开发人员都会采用线程池来高效执行这些小任务。然而,有一种任务,例如,对超过1000万个元素的数组进行排序,这种任务本身可以并发执行,但如何拆解成小任务需要在任务执行的过程中动态拆分。这样,大任务可以拆成小任务,小任务还可以继续拆成更小的任务,最后把任务的结果汇总合并,得到最终结果,这种模型就是Fork/Join模型。

Java7引入了Fork/Join框架,我们通过RecursiveTask这个类就可以方便地实现Fork/Join模式。

例如,对一个大数组进行并行求和的RecursiveTask,就可以这样编写:

class SumTask extends RecursiveTask<Long> {

    static final int THRESHOLD = 100;
    long[] array;
    int start;
    int end;

    SumTask(long[] array, int start, int end) {
    this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // 如果任务足够小,直接计算:
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println(String.format("compute %d~%d = %d", start, end, sum));
            return sum;
        }
        // 任务太大,一分为二:
        int middle = (end + start) / 2;
        System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
        SumTask subtask1 = new SumTask(this.array, start, middle);
        SumTask subtask2 = new SumTask(this.array, middle, end);
        invokeAll(subtask1, subtask2);
        Long subresult1 = subtask1.join();
        Long subresult2 = subtask2.join();
        Long result = subresult1 + subresult2;
        System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
        return result;
    }
}

编写这个Fork/Join任务的关键在于,在执行任务的compute()方法内部,先判断任务是不是足够小,如果足够小,就直接计算并返回结果(注意模拟了1秒延时),否则,把自身任务一拆为二,分别计算两个子任务,再返回两个子任务的结果之和。

最后写一个main()方法测试:

public static void main(String[] args) throws Exception {
    // 创建随机数组成的数组:
    long[] array = new long[400];
    fillRandom(array);
    // fork/join task:
    ForkJoinPool fjp = new ForkJoinPool(4); // 最大并发数4
    ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
    long startTime = System.currentTimeMillis();
    Long result = fjp.invoke(task);
    long endTime = System.currentTimeMillis();
    System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}

关键代码是fjp.invoke(task)来提交一个Fork/Join任务并发执行,然后获得异步执行的结果。

我们设置任务的最小阀值是100,当提交一个400大小的任务时,在4核CPU上执行,会一分为二,再二分为四,每个最小子任务的执行时间是1秒,由于是并发4个子任务执行,整个任务最终执行时间大约为1秒。

新手在编写Fork/Join任务时,往往用搜索引擎搜到一个例子,然后就照着例子写出了下面的代码:

protected Long compute() {
    if (任务足够小?) {
        return computeDirect();
    }
    // 任务太大,一分为二:
    SumTask subtask1 = new SumTask(...);
    SumTask subtask2 = new SumTask(...);
    // 分别对子任务调用fork():
    subtask1.fork();
    subtask2.fork();
    // 合并结果:
    Long subresult1 = subtask1.join();
    Long subresult2 = subtask2.join();
    return subresult1 + subresult2;
}

很遗憾,这种写法是错!误!的!这样写没有正确理解Fork/Join模型的任务执行逻辑。

JDK用来执行Fork/Join任务的工作线程池大小等于CPU核心数。在一个4核CPU上,最多可以同时执行4个子任务。对400个元素的数组求和,执行时间应该为1秒。但是,换成上面的代码,执行时间却是两秒。

这是因为执行compute()方法的线程本身也是一个Worker线程,当对两个子任务调用fork()时,这个Worker线程就会把任务分配给另外两个Worker,但是它自己却停下来等待不干活了!这样就白白浪费了Fork/Join线程池中的一个Worker线程,导致了4个子任务至少需要7个线程才能并发执行。

打个比方,假设一个酒店有400个房间,一共有4名清洁工,每个工人每天可以打扫100个房间,这样,4个工人满负荷工作时,400个房间全部打扫完正好需要1天。

Fork/Join的工作模式就像这样:首先,工人甲被分配了400个房间的任务,他一看任务太多了自己一个人不行,所以先把400个房间拆成两个200,然后叫来乙,把其中一个200分给乙。

紧接着,甲和乙再发现200也是个大任务,于是甲继续把200分成两个100,并把其中一个100分给丙,类似的,乙会把其中一个100分给丁,这样,最终4个人每人分到100个房间,并发执行正好是1天。

如果换一种写法:

// 分别对子任务调用fork():
subtask1.fork();
subtask2.fork();

这个任务就分!错!了!

比如甲把400分成两个200后,这种写法相当于甲把一个200分给乙,把另一个200分给丙,然后,甲成了监工,不干活,等乙和丙干完了他直接汇报工作。乙和丙在把200分拆成两个100的过程中,他俩又成了监工,这样,本来只需要4个工人的活,现在需要7个工人才能1天内完成,其中有3个是不干活的。

其实,我们查看JDK的invokeAll()方法的源码就可以发现,invokeAll的N个任务中,其中N-1个任务会使用fork()交给其它线程执行,但是,它还会留一个任务自己执行,这样,就充分利用了线程池,保证没有空闲的不干活的线程。

关于服务分层的意见收集

我先起个头,说一下我的想法。
服务分为4层,并将部分中间件服务划为第一层,从底层向上层如下:
第一层*中间件服务:可以被任何服务调用的服务,为了和以下两种服务区分,可称其为中间件组件
第二层*底层独立微服务:不需要调用别的微服务的服务(但可调用中间件服务),例如财务微服务、基础数据微服务
第三层*业务微服务:从业务角度划分出来的微服务,可调用上面两种服务,但不可调用业务微服务
第四层*组合业务微服务:为抽象重复聚合业务微服务而形成的第二级的业务微服务,是对于业务微服务的包装,可调用上面三种服务(同样不可调用组合业务微服务)

Spring MVC 更灵活的控制 json 返回(自定义过滤字段)

前言:

其实这篇文章也不算完全的原创,因为是看了https://my.oschina.net/diamondfsd/blog/836727实践后做的总结,并且文章后面附上源码下载链接。

我们要做什么?

我们要在springmvc的控制层,在返回字符串的时候,动态改变一下到底要返回哪些字符串,比如我们要返回实体Article,他里面有4个属性我们只想返回其中的3个如图

如何实现?很简单加一个注解,如图,传入实体类型和拦截的属性名

当然这个注解不是springMVC自带的,是我们自定义的,下面就告诉大家如何实现。

1  首先我们要配置一个拦截器,拦截控制层的请求如图

2  实现HandlerMethodReturnValueHandler接口,其实就实现两个方法,一个是拦截后判定是否有对应的注解,也就是@JSON注解,如果有,就执行第二个方法,解析注解中的数据进行一些操作如图

3 我们定义一个类专门进行注解的解析,设置过滤器,和返回json 被jsonReturnHandler调用

4 过滤器

调用关系

源码(只包含工具类):https://pan.baidu.com/s/1jHCnySI

整个程序:https://pan.baidu.com/s/1dFgPNe5

总结:其实挺简单的,就是通过自定义注解,和拦截器实现注解的识别读取,通过jackson实现转换json,通过拦截器,拦截不需要的属性

————————————————chenchen

微信扫描公众号二维码,用户的网站账号自动登陆网站

 

微信公众号是在2012年8月份正式上线的。很荣幸,我在2013年底开始接触到了它并自己学习摸索开发了一些简单的功能,比如自动回复、自定义菜单。自动回复的时候如果是英文单词我还会帮用户翻译,类似于一个英语词典。最开始就是照着简洁的官方文档来做,慢慢摸索。不过后来接触多了慢慢发现,公众号的架构与开发者逻辑是很严谨也很安全的。举个简单的例子,比如要开发自定义菜单功能,需要用秘钥A和秘钥B去拿秘钥C,在后台管理里配置安全域名后,才可以用秘钥C用特定的方式去访问自定义菜单的接口。微信还有一个开放平台,我也使用过,那个就更复杂一点。

 

切入正题,后来换了一家公司,他们正好也会做一些公众号的业务,我就帮他们的网站做了一个个性的登录功能—-公众号扫码登录。这个登录不是“授权登录”,而是先把公众号与网站上自己的账号做绑定,然后网站的登录处选择公众号扫码登录,用户如果未关注则需要先关注公众号,然后绑定账号,如果已经关注且绑定了,扫码后公众号里则回复登录提示,点击后网站直接登录已经绑定的账号。关键点就在这个绑定的网站账号上,如果是授权登录,公众号就可以直接获取你的用户信息并登录,而扫码登录需要知道你在哪台电脑登录、让谁登录、何时登录。

下面是简易的技术流程图:

1

*角色:网站(web)、公众号项目(mobile)、用户(user)
登陆步骤:

1.网站在约定的场景id随机数里生成带参数二维码,供用户扫码(生成二维码以后网站不断轮询)
2.用户未关注时,让他关注公众号,并绑定自己的网站账号;已关注时则判断是否绑定账号,如果未绑定,跳转到绑定页面

3.已关注并绑定时直接回复消息“点击登陆网站”,用户点击登陆按钮,修改状态state=1(默认是0)
4.当网站查询到用户的登陆状态变化后(state=1),立刻返回给网站前端账号信息(同时恢复状态state=0),未查询到则返回-1(账号信息RC4加密)
5.当网站收到接口返回的账号信息后立刻将该账号登陆

synchonized加锁的虚拟机实现

synchonized实现有两种方式,一种是直接加在方法上,对整个方法进行加锁。另一种是对方法内的某一个代码块进行加锁,那么这两种加锁方式在虚拟机内的实现是怎样的呢。

对方法直接进行加锁:方法级的同步时隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之间。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要先成功持有管程,然后才能执行方法,最后当方法完成时释放管程(无论是否正常完成都会释放)。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个方法所持有的管程将在异常抛到同步方法之外时自动释放。

对方法内部的某一个代码块进行加锁:java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要javac 编译器和java虚拟机两者共同协作支持。在代码块执行开始的时候,将会调用monitorenter指令,当代码块结束时将调用monitorexit指令,编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有异常,它的目的就是用来执行monitorexit指令。